@patternfly/chatbot 2.2.0-prerelease.11 → 2.2.0-prerelease.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +3 -1
  2. package/dist/cjs/ChatbotHeader/ChatbotHeaderCloseButton.js +3 -1
  3. package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.js +3 -1
  4. package/dist/cjs/ChatbotHeader/ChatbotHeaderOptionsDropdown.js +3 -1
  5. package/dist/cjs/ChatbotHeader/ChatbotHeaderSelectorDropdown.js +3 -1
  6. package/dist/cjs/ChatbotToggle/ChatbotToggle.js +3 -1
  7. package/dist/cjs/Message/Message.d.ts +12 -1
  8. package/dist/cjs/Message/Message.js +11 -6
  9. package/dist/cjs/Message/QuickResponse/QuickResponse.d.ts +3 -1
  10. package/dist/cjs/Message/QuickResponse/QuickResponse.js +2 -1
  11. package/dist/cjs/Message/UserFeedback/CloseButton.d.ts +10 -0
  12. package/dist/cjs/Message/UserFeedback/CloseButton.js +14 -0
  13. package/dist/cjs/Message/UserFeedback/UserFeedback.d.ts +39 -0
  14. package/dist/cjs/Message/UserFeedback/UserFeedback.js +55 -0
  15. package/dist/cjs/Message/UserFeedback/UserFeedback.test.d.ts +1 -0
  16. package/dist/cjs/Message/UserFeedback/UserFeedback.test.js +146 -0
  17. package/dist/cjs/Message/UserFeedback/UserFeedbackComplete.d.ts +42 -0
  18. package/dist/cjs/Message/UserFeedback/UserFeedbackComplete.js +117 -0
  19. package/dist/cjs/Message/UserFeedback/UserFeedbackComplete.test.d.ts +1 -0
  20. package/dist/cjs/Message/UserFeedback/UserFeedbackComplete.test.js +249 -0
  21. package/dist/cjs/MessageBar/AttachButton.js +3 -1
  22. package/dist/cjs/MessageBar/SendButton.js +3 -1
  23. package/dist/cjs/MessageBar/StopButton.js +3 -1
  24. package/dist/cjs/ResponseActions/ResponseActionButton.d.ts +4 -1
  25. package/dist/cjs/ResponseActions/ResponseActionButton.js +21 -6
  26. package/dist/cjs/ResponseActions/ResponseActions.d.ts +8 -2
  27. package/dist/cjs/ResponseActions/ResponseActions.js +7 -7
  28. package/dist/css/main.css +69 -11
  29. package/dist/css/main.css.map +1 -1
  30. package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.js +3 -1
  31. package/dist/esm/ChatbotHeader/ChatbotHeaderCloseButton.js +3 -1
  32. package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.js +3 -1
  33. package/dist/esm/ChatbotHeader/ChatbotHeaderOptionsDropdown.js +3 -1
  34. package/dist/esm/ChatbotHeader/ChatbotHeaderSelectorDropdown.js +3 -1
  35. package/dist/esm/ChatbotToggle/ChatbotToggle.js +3 -1
  36. package/dist/esm/Message/Message.d.ts +12 -1
  37. package/dist/esm/Message/Message.js +8 -3
  38. package/dist/esm/Message/QuickResponse/QuickResponse.d.ts +3 -1
  39. package/dist/esm/Message/QuickResponse/QuickResponse.js +2 -1
  40. package/dist/esm/Message/UserFeedback/CloseButton.d.ts +10 -0
  41. package/dist/esm/Message/UserFeedback/CloseButton.js +9 -0
  42. package/dist/esm/Message/UserFeedback/UserFeedback.d.ts +39 -0
  43. package/dist/esm/Message/UserFeedback/UserFeedback.js +50 -0
  44. package/dist/esm/Message/UserFeedback/UserFeedback.test.d.ts +1 -0
  45. package/dist/esm/Message/UserFeedback/UserFeedback.test.js +141 -0
  46. package/dist/esm/Message/UserFeedback/UserFeedbackComplete.d.ts +42 -0
  47. package/dist/esm/Message/UserFeedback/UserFeedbackComplete.js +112 -0
  48. package/dist/esm/Message/UserFeedback/UserFeedbackComplete.test.d.ts +1 -0
  49. package/dist/esm/Message/UserFeedback/UserFeedbackComplete.test.js +244 -0
  50. package/dist/esm/MessageBar/AttachButton.js +3 -1
  51. package/dist/esm/MessageBar/SendButton.js +3 -1
  52. package/dist/esm/MessageBar/StopButton.js +3 -1
  53. package/dist/esm/ResponseActions/ResponseActionButton.d.ts +4 -1
  54. package/dist/esm/ResponseActions/ResponseActionButton.js +18 -3
  55. package/dist/esm/ResponseActions/ResponseActions.d.ts +8 -2
  56. package/dist/esm/ResponseActions/ResponseActions.js +7 -7
  57. package/dist/tsconfig.tsbuildinfo +1 -1
  58. package/package.json +1 -1
  59. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithFeedback.tsx +71 -0
  60. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithFeedbackTimeout.tsx +27 -0
  61. package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +37 -7
  62. package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +3 -6
  63. package/patternfly-docs/content/extensions/chatbot/examples/demos/AttachmentDemos.md +14 -0
  64. package/patternfly-docs/content/extensions/chatbot/examples/demos/Feedback.tsx +104 -0
  65. package/src/AttachMenu/AttachMenu.scss +1 -1
  66. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryDropdown.tsx +7 -1
  67. package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.scss +8 -1
  68. package/src/ChatbotHeader/ChatbotHeaderCloseButton.tsx +7 -1
  69. package/src/ChatbotHeader/ChatbotHeaderMenu.tsx +7 -1
  70. package/src/ChatbotHeader/ChatbotHeaderOptionsDropdown.tsx +8 -1
  71. package/src/ChatbotHeader/ChatbotHeaderSelectorDropdown.tsx +8 -1
  72. package/src/ChatbotModal/ChatbotModal.scss +1 -1
  73. package/src/ChatbotToggle/ChatbotToggle.tsx +6 -1
  74. package/src/CodeModal/CodeModal.scss +1 -1
  75. package/src/FileDetails/FileDetails.scss +1 -1
  76. package/src/Message/CodeBlockMessage/CodeBlockMessage.scss +1 -1
  77. package/src/Message/Message.scss +1 -1
  78. package/src/Message/Message.tsx +24 -2
  79. package/src/Message/QuickResponse/QuickResponse.tsx +6 -2
  80. package/src/Message/UserFeedback/CloseButton.tsx +21 -0
  81. package/src/Message/UserFeedback/UserFeedback.scss +53 -0
  82. package/src/Message/UserFeedback/UserFeedback.test.tsx +257 -0
  83. package/src/Message/UserFeedback/UserFeedback.tsx +132 -0
  84. package/src/Message/UserFeedback/UserFeedbackComplete.test.tsx +255 -0
  85. package/src/Message/UserFeedback/UserFeedbackComplete.tsx +211 -0
  86. package/src/MessageBar/AttachButton.tsx +2 -0
  87. package/src/MessageBar/SendButton.tsx +2 -0
  88. package/src/MessageBar/StopButton.tsx +2 -0
  89. package/src/ResponseActions/ResponseActionButton.tsx +14 -2
  90. package/src/ResponseActions/ResponseActions.tsx +26 -2
  91. package/src/Settings/Settings.scss +2 -2
  92. package/src/SourceDetailsMenuItem/SourceDetailsMenuItem.scss +1 -1
  93. package/src/main.scss +1 -0
@@ -0,0 +1,255 @@
1
+ import React from 'react';
2
+ import { act, render, screen } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import userEvent from '@testing-library/user-event';
5
+ import UserFeedbackComplete from './UserFeedbackComplete';
6
+
7
+ describe('UserFeedbackComplete', () => {
8
+ it('should render correctly', () => {
9
+ render(<UserFeedbackComplete timestamp="12/12/12" />);
10
+ expect(screen.getByText('Feedback submitted')).toBeTruthy();
11
+ screen.getByText(/We've received your response. Thank you for sharing your feedback!/i);
12
+ expect(screen.queryByRole('button', { name: /Close/i })).toBeFalsy();
13
+ });
14
+ it('should render different title correctly', () => {
15
+ render(<UserFeedbackComplete timestamp="12/12/12" title="Thanks!" />);
16
+ expect(screen.getByText('Thanks!')).toBeTruthy();
17
+ screen.getByText(/We've received your response. Thank you for sharing your feedback!/i);
18
+ });
19
+ it('should render different string body correctly', () => {
20
+ render(<UserFeedbackComplete timestamp="12/12/12" body="Thanks!" />);
21
+ expect(screen.getByText('Feedback submitted')).toBeTruthy();
22
+ expect(screen.getByText('Thanks!')).toBeTruthy();
23
+ });
24
+ it('should render different node body correctly', () => {
25
+ render(<UserFeedbackComplete timestamp="12/12/12" body={<div>Thanks!</div>} />);
26
+ expect(screen.getByText('Feedback submitted')).toBeTruthy();
27
+ expect(screen.getByText('Thanks!')).toBeTruthy();
28
+ });
29
+ it('should handle onClose correctly', async () => {
30
+ const spy = jest.fn();
31
+ render(<UserFeedbackComplete timestamp="12/12/12" onClose={spy} />);
32
+ const closeButton = screen.getByRole('button', { name: 'Close feedback for message received at 12/12/12' });
33
+ expect(closeButton).toBeTruthy();
34
+ await userEvent.click(closeButton);
35
+ expect(spy).toHaveBeenCalledTimes(1);
36
+ });
37
+ it('should be able to change close button aria label', () => {
38
+ const spy = jest.fn();
39
+ render(<UserFeedbackComplete timestamp="12/12/12" onClose={spy} closeButtonAriaLabel="Ima button" />);
40
+ expect(screen.getByRole('button', { name: /Ima button/i })).toBeTruthy();
41
+ });
42
+ it('should handle className', async () => {
43
+ render(<UserFeedbackComplete timestamp="12/12/12" className="test" data-testid="card" />);
44
+ expect(screen.getByTestId('card')).toHaveClass('test');
45
+ });
46
+ it('should apply id', async () => {
47
+ render(<UserFeedbackComplete timestamp="12/12/12" id="test" data-testid="card" />);
48
+ expect(screen.getByTestId('card').parentElement).toHaveAttribute('id', 'test');
49
+ });
50
+ it('renders with no timeout by default', () => {
51
+ jest.useFakeTimers();
52
+ render(<UserFeedbackComplete timestamp="12/12/12" />);
53
+ act(() => {
54
+ jest.advanceTimersByTime(8000);
55
+ });
56
+ expect(screen.getByText('Feedback submitted')).toBeVisible();
57
+ jest.useRealTimers();
58
+ });
59
+ it('should handle timeout correctly after 8000ms when timeout = true', async () => {
60
+ jest.useFakeTimers();
61
+ render(<UserFeedbackComplete timestamp="12/12/12" timeout />);
62
+ act(() => {
63
+ jest.advanceTimersByTime(7999);
64
+ });
65
+ expect(screen.getByText('Feedback submitted')).toBeVisible();
66
+ act(() => {
67
+ jest.advanceTimersByTime(1);
68
+ });
69
+ expect(screen.queryByText('Feedback submitted')).not.toBeInTheDocument();
70
+ jest.useRealTimers();
71
+ });
72
+ it('should handle timeout correctly when timeout = numeric value', async () => {
73
+ jest.useFakeTimers();
74
+ render(<UserFeedbackComplete timestamp="12/12/12" timeout={300} />);
75
+ act(() => {
76
+ jest.advanceTimersByTime(299);
77
+ });
78
+ expect(screen.getByText('Feedback submitted')).toBeVisible();
79
+ act(() => {
80
+ jest.advanceTimersByTime(1);
81
+ });
82
+ expect(screen.queryByText('Feedback submitted')).not.toBeInTheDocument();
83
+ jest.useRealTimers();
84
+ });
85
+ it('does not get removed on timeout if the user is focused on the card', async () => {
86
+ const user = userEvent.setup({
87
+ advanceTimers: (delay) => jest.advanceTimersByTime(delay)
88
+ });
89
+ jest.useFakeTimers();
90
+ render(<UserFeedbackComplete timestamp="12/12/12" timeout data-testid="card" />);
91
+ expect(screen.getByText('Feedback submitted')).toBeTruthy();
92
+ await user.click(screen.getByTestId('card'));
93
+ act(() => {
94
+ jest.advanceTimersByTime(8000);
95
+ });
96
+ expect(screen.getByText('Feedback submitted')).toBeTruthy();
97
+ jest.useRealTimers();
98
+ });
99
+ it('does not remove the card on timeout if the user is hovered over it', async () => {
100
+ const user = userEvent.setup({
101
+ advanceTimers: (delay) => jest.advanceTimersByTime(delay)
102
+ });
103
+ jest.useFakeTimers();
104
+ render(<UserFeedbackComplete timestamp="12/12/12" timeout data-testid="card" />);
105
+ const card = screen.getByTestId('card');
106
+ await user.hover(card);
107
+ act(() => {
108
+ jest.advanceTimersByTime(8000);
109
+ });
110
+ expect(card).toBeVisible();
111
+ jest.useRealTimers();
112
+ });
113
+ it('removes the card after the user removes focus from the card and 3000ms have passed', async () => {
114
+ const user = userEvent.setup({
115
+ advanceTimers: (delay) => jest.advanceTimersByTime(delay)
116
+ });
117
+ jest.useFakeTimers();
118
+ render(
119
+ <div>
120
+ <input />
121
+ <UserFeedbackComplete timestamp="12/12/12" timeout data-testid="card" />
122
+ </div>
123
+ );
124
+ const card = screen.getByTestId('card');
125
+ await user.click(card);
126
+ act(() => {
127
+ jest.advanceTimersByTime(8000);
128
+ });
129
+ await user.click(screen.getByRole('textbox'));
130
+ act(() => {
131
+ jest.advanceTimersByTime(3000);
132
+ });
133
+ expect(screen.queryByText('Feedback submitted')).not.toBeInTheDocument();
134
+ jest.useRealTimers();
135
+ });
136
+
137
+ it('removes the card after the user removes hover from the card and 3000ms have passed', async () => {
138
+ const user = userEvent.setup({
139
+ advanceTimers: (delay) => jest.advanceTimersByTime(delay)
140
+ });
141
+ jest.useFakeTimers();
142
+ render(
143
+ <div>
144
+ <input />
145
+ <UserFeedbackComplete timestamp="12/12/12" timeout data-testid="card" />
146
+ </div>
147
+ );
148
+ const card = screen.getByTestId('card');
149
+ await user.hover(card);
150
+ act(() => {
151
+ jest.advanceTimersByTime(8000);
152
+ });
153
+ await user.hover(screen.getByRole('textbox'));
154
+ act(() => {
155
+ jest.advanceTimersByTime(3000);
156
+ });
157
+ expect(screen.queryByText('Feedback submitted')).not.toBeInTheDocument();
158
+ jest.useRealTimers();
159
+ });
160
+
161
+ it('removes the card after the user removes hover from the card and timeoutAnimation time has passed', async () => {
162
+ const user = userEvent.setup({
163
+ advanceTimers: (delay) => jest.advanceTimersByTime(delay)
164
+ });
165
+ jest.useFakeTimers();
166
+ render(
167
+ <div>
168
+ <input />
169
+ <UserFeedbackComplete timestamp="12/12/12" timeout data-testid="card" timeoutAnimation={1000} />
170
+ </div>
171
+ );
172
+ const card = screen.getByTestId('card');
173
+ await user.hover(card);
174
+ act(() => {
175
+ jest.advanceTimersByTime(8000);
176
+ });
177
+ await user.hover(screen.getByRole('textbox'));
178
+ act(() => {
179
+ jest.advanceTimersByTime(1000);
180
+ });
181
+ expect(screen.queryByText('Feedback submitted')).not.toBeInTheDocument();
182
+ jest.useRealTimers();
183
+ });
184
+ it('does not call the onTimeout callback before the timeout period has expired', () => {
185
+ const spy = jest.fn();
186
+ jest.useFakeTimers();
187
+ render(<UserFeedbackComplete timestamp="12/12/12" timeout data-testid="card" onTimeout={spy} />);
188
+ act(() => {
189
+ jest.advanceTimersByTime(7999);
190
+ });
191
+ expect(spy).not.toHaveBeenCalled();
192
+ jest.useRealTimers();
193
+ });
194
+ it('calls the onTimeout callback after the timeout period has expired', () => {
195
+ jest.useFakeTimers();
196
+ const spy = jest.fn();
197
+ render(<UserFeedbackComplete timestamp="12/12/12" timeout data-testid="card" onTimeout={spy} />);
198
+ act(() => {
199
+ jest.advanceTimersByTime(8000);
200
+ });
201
+ expect(spy).toHaveBeenCalledTimes(1);
202
+ jest.useRealTimers();
203
+ });
204
+
205
+ it('renders without aria-live and aria-atomic attributes by default', () => {
206
+ render(<UserFeedbackComplete timestamp="12/12/12" timeout data-testid="card" />);
207
+ const card = screen.getByTestId('card').parentElement;
208
+ expect(card).not.toHaveAttribute('aria-live');
209
+ expect(card).not.toHaveAttribute('aria-atomic');
210
+ });
211
+
212
+ it('has an aria-live value of polite and aria-atomic value of false when isLiveRegion = true', () => {
213
+ render(<UserFeedbackComplete timestamp="12/12/12" timeout data-testid="card" isLiveRegion />);
214
+ const card = screen.getByTestId('card').parentElement;
215
+ expect(card).toHaveAttribute('aria-live', 'polite');
216
+ expect(card).toHaveAttribute('aria-atomic', 'false');
217
+ });
218
+ it('calls onMouseEnter appropriately', async () => {
219
+ const spy = jest.fn();
220
+ const user = userEvent.setup();
221
+ render(
222
+ <div>
223
+ <input />
224
+ <UserFeedbackComplete timestamp="12/12/12" data-testid="card" onMouseEnter={spy} />
225
+ </div>
226
+ );
227
+ const card = screen.getByTestId('card');
228
+ expect(spy).toHaveBeenCalledTimes(0);
229
+ await user.hover(card);
230
+ expect(spy).toHaveBeenCalledTimes(1);
231
+ });
232
+ it('calls onMouseLeave appropriately', async () => {
233
+ const spy = jest.fn();
234
+ const user = userEvent.setup();
235
+ render(
236
+ <div>
237
+ <input />
238
+ <UserFeedbackComplete timestamp="12/12/12" data-testid="card" onMouseLeave={spy} />
239
+ </div>
240
+ );
241
+ const card = screen.getByTestId('card');
242
+ expect(spy).toHaveBeenCalledTimes(0);
243
+ await user.hover(card);
244
+ await user.hover(screen.getByRole('textbox'));
245
+ expect(spy).toHaveBeenCalledTimes(1);
246
+ });
247
+ it('should focus on load by default', () => {
248
+ render(<UserFeedbackComplete timestamp="12/12/12" data-testid="card" />);
249
+ expect(screen.getByTestId('card').parentElement).toHaveFocus();
250
+ });
251
+ it('should not focus on load if focusOnLoad = false', () => {
252
+ render(<UserFeedbackComplete timestamp="12/12/12" data-testid="card" focusOnLoad={false} />);
253
+ expect(screen.getByTestId('card').parentElement).not.toHaveFocus();
254
+ });
255
+ });
@@ -0,0 +1,211 @@
1
+ // ============================================================================
2
+ // Chatbot Main - Messages - Feedback Complete Card
3
+ // ============================================================================
4
+ import React from 'react';
5
+
6
+ // Import PatternFly components
7
+ import { Card, CardBody, CardHeader, CardProps, CardTitle, OUIAProps, useOUIAProps } from '@patternfly/react-core';
8
+ import CloseButton from './CloseButton';
9
+
10
+ export interface UserFeedbackCompleteProps extends Omit<CardProps, 'ref'>, OUIAProps {
11
+ /** Additional classes for the pagination navigation container. */
12
+ className?: string;
13
+ /** Substitute for the English phrase "Thank you". */
14
+ title?: string;
15
+ /** Substitute for the English phrase "You have successfully sent your feedback! Thank you for responding." */
16
+ body?: string | React.ReactNode;
17
+ /** Callback function for when close button is clicked */
18
+ onClose?: () => void;
19
+ /** Aria label for close button */
20
+ closeButtonAriaLabel?: string;
21
+ /** Function to be executed on timeout. Relevant when the timeout prop is set. */
22
+ onTimeout?: () => void;
23
+ /** If set to true, the timeout is 8000 milliseconds. If a number is provided, card will
24
+ * be dismissed after that amount of time in milliseconds.
25
+ */
26
+ timeout?: number | boolean;
27
+ /** If the user hovers over the card and `timeout` expires, this is how long to wait
28
+ * before finally dismissing the alert.
29
+ */
30
+ timeoutAnimation?: number;
31
+ /** Callback for when mouse hovers over card */
32
+ onMouseEnter?: (e: React.MouseEvent<HTMLDivElement>) => void;
33
+ /** Callback for when mouse stops hovering over card */
34
+ onMouseLeave?: (e: React.MouseEvent<HTMLDivElement>) => void;
35
+ /** Value to overwrite the randomly generated data-ouia-component-id.*/
36
+ ouiaId?: number | string;
37
+ /** Set the value of data-ouia-safe. Only set to true when the component is in a static state, i.e. no animations are occurring. At all other times, this value must be false. */
38
+ ouiaSafe?: boolean;
39
+ /** Flag to indicate if the card is in a live region. */
40
+ isLiveRegion?: boolean;
41
+ /** Uniquely identifies the completion card. */
42
+ id?: string;
43
+ /** Whether to focus card on load */
44
+ focusOnLoad?: boolean;
45
+ /** Timestamp passed in by Message for more context in aria announcements */
46
+ timestamp?: string;
47
+ }
48
+
49
+ const UserFeedbackComplete: React.FunctionComponent<UserFeedbackCompleteProps> = ({
50
+ className,
51
+ title = 'Feedback submitted',
52
+ body = "We've received your response. Thank you for sharing your feedback!",
53
+ timestamp,
54
+ timeout = false,
55
+ timeoutAnimation = 3000,
56
+ onTimeout,
57
+ onClose,
58
+ closeButtonAriaLabel = `Close feedback for message received at ${timestamp}`,
59
+ onMouseEnter,
60
+ onMouseLeave,
61
+ ouiaId,
62
+ ouiaSafe,
63
+ isLiveRegion,
64
+ id,
65
+ focusOnLoad = true,
66
+ ...props
67
+ }: UserFeedbackCompleteProps) => {
68
+ const [timedOut, setTimedOut] = React.useState(false);
69
+ const [timedOutAnimation, setTimedOutAnimation] = React.useState(true);
70
+ const [isMouseOver, setIsMouseOver] = React.useState<boolean | undefined>();
71
+ const [containsFocus, setContainsFocus] = React.useState<boolean | undefined>();
72
+ const dismissed = timedOut && timedOutAnimation && !isMouseOver && !containsFocus;
73
+ const divRef = React.useRef<HTMLDivElement>(null);
74
+ const ouiaProps = useOUIAProps('User Feedback Complete', ouiaId, ouiaSafe);
75
+
76
+ React.useEffect(() => {
77
+ if (focusOnLoad) {
78
+ divRef.current?.focus();
79
+ }
80
+ }, []);
81
+
82
+ React.useEffect(() => {
83
+ const calculatedTimeout = timeout === true ? 8000 : Number(timeout);
84
+ if (calculatedTimeout > 0) {
85
+ const timer = setTimeout(() => setTimedOut(true), calculatedTimeout);
86
+ return () => clearTimeout(timer);
87
+ }
88
+ }, [timeout]);
89
+
90
+ React.useEffect(() => {
91
+ const onDocumentFocus = () => {
92
+ if (divRef.current) {
93
+ if (divRef.current.contains(document.activeElement)) {
94
+ setContainsFocus(true);
95
+ setTimedOutAnimation(false);
96
+ } else if (containsFocus) {
97
+ setContainsFocus(false);
98
+ }
99
+ }
100
+ };
101
+ document.addEventListener('focus', onDocumentFocus, true);
102
+ return () => document.removeEventListener('focus', onDocumentFocus, true);
103
+ }, [containsFocus]);
104
+
105
+ React.useEffect(() => {
106
+ if (containsFocus === false || isMouseOver === false) {
107
+ const timer = setTimeout(() => setTimedOutAnimation(true), timeoutAnimation);
108
+ return () => clearTimeout(timer);
109
+ }
110
+ }, [containsFocus, isMouseOver, timeoutAnimation]);
111
+
112
+ React.useEffect(() => {
113
+ dismissed && onTimeout && onTimeout();
114
+ }, [dismissed, onTimeout]);
115
+
116
+ if (dismissed) {
117
+ return null;
118
+ }
119
+
120
+ const myOnMouseEnter = (ev: React.MouseEvent<HTMLDivElement>) => {
121
+ setIsMouseOver(true);
122
+ setTimedOutAnimation(false);
123
+ onMouseEnter && onMouseEnter(ev);
124
+ };
125
+
126
+ const myOnMouseLeave = (ev: React.MouseEvent<HTMLDivElement>) => {
127
+ setIsMouseOver(false);
128
+ onMouseLeave && onMouseLeave(ev);
129
+ };
130
+
131
+ return (
132
+ /* card does not have ref forwarding; hence wrapper div */
133
+ <div
134
+ ref={divRef}
135
+ onMouseEnter={myOnMouseEnter}
136
+ onMouseLeave={myOnMouseLeave}
137
+ {...(isLiveRegion && {
138
+ 'aria-live': 'polite',
139
+ 'aria-atomic': 'false'
140
+ })}
141
+ id={id}
142
+ tabIndex={0}
143
+ aria-label={title}
144
+ {...ouiaProps}
145
+ >
146
+ <Card className={`pf-chatbot__feedback-card ${className ? className : ''}`} {...props}>
147
+ <CardHeader
148
+ actions={
149
+ /* eslint-disable indent */
150
+ onClose
151
+ ? {
152
+ actions: <CloseButton onClose={onClose} ariaLabel={closeButtonAriaLabel} />
153
+ }
154
+ : undefined
155
+ /* eslint-enable indent */
156
+ }
157
+ ></CardHeader>
158
+ <div className="pf-chatbot__feedback-complete-body">
159
+ <div className="pf-chatbot__feedback-complete-image">
160
+ <svg width="60" height="64" viewBox="0 0 60 64" fill="none" xmlns="http://www.w3.org/2000/svg">
161
+ <path
162
+ d="M53.0555 21.5975H6.94323C3.57013 21.5975 0.835693 24.3319 0.835693 27.705V57.8925C0.835693 61.2656 3.57013 64 6.94323 64H53.0555C56.4286 64 59.1631 61.2656 59.1631 57.8925V27.705C59.1631 24.3319 56.4286 21.5975 53.0555 21.5975Z"
163
+ fill="#F8AE54"
164
+ />
165
+ <path
166
+ d="M55.8973 19.8247C52.5894 15.7926 29.9992 0 29.9992 0C29.9992 0 7.40996 15.7926 4.10102 19.8247C0.79312 23.8568 0.835476 25.7184 0.835476 27.8899H59.1629C59.1629 25.7184 59.2052 23.8578 55.8973 19.8257V19.8247Z"
167
+ fill="#FFCC17"
168
+ />
169
+ <g>
170
+ <path d="M53.0567 8.48981H6.94336V61.8388H53.0567V8.48981Z" fill="#F2F2F2" />
171
+ </g>
172
+ <path d="M51.6589 7.49908H8.34204V60.8481H51.6589V7.49908Z" fill="white" />
173
+ <path
174
+ d="M0.835693 29.1296V57.8925C0.835693 59.2375 1.27165 60.4803 2.00823 61.4896L23.0303 43.5462L0.835693 29.1296Z"
175
+ fill="#FFCC17"
176
+ />
177
+ <path
178
+ d="M36.9695 43.5472L57.9905 61.4907C58.7271 60.4813 59.1631 59.2386 59.1631 57.8935V29.1306L36.9685 43.5472H36.9695Z"
179
+ fill="#FFF4CC"
180
+ />
181
+ <path
182
+ d="M0.835693 57.8925V57.8067L22.4146 42.7992L29.9994 37.5244L37.5842 42.7992L59.1641 57.8067V57.8925C59.1641 61.2665 56.4296 64 53.0566 64H6.94323C3.57024 64 0.835693 61.2665 0.835693 57.8925Z"
183
+ fill="#FFE072"
184
+ />
185
+ <g>
186
+ <path d="M22.1563 42.978L0.835693 57.8067V56.6993L22.1563 42.978Z" fill="#FEF07C" />
187
+ </g>
188
+ <g>
189
+ <path d="M37.8425 42.978L59.1631 57.8067V56.6993L37.8425 42.978Z" fill="#FEF07C" />
190
+ </g>
191
+ <path
192
+ d="M37.8037 32.2373C42.1136 27.9273 42.1136 20.9395 37.8037 16.6295C33.4937 12.3196 26.5059 12.3196 22.196 16.6295C17.886 20.9395 17.886 27.9273 22.196 32.2373C26.5059 36.5472 33.4937 36.5472 37.8037 32.2373Z"
193
+ fill="#0066CC"
194
+ />
195
+ <path
196
+ d="M27.7803 30.1276C27.6098 30.1276 27.4497 30.0614 27.3298 29.9406L22.9465 25.5562C22.8267 25.4364 22.7595 25.2762 22.7595 25.1068C22.7595 24.9374 22.8256 24.7762 22.9465 24.6554L24.2379 23.364C24.3577 23.2442 24.5178 23.177 24.6883 23.177C24.8587 23.177 25.0168 23.2431 25.1377 23.363L27.7803 26.0056L34.861 18.9259C34.9808 18.8061 35.1409 18.7389 35.3103 18.7389C35.4798 18.7389 35.6389 18.8051 35.7597 18.9239L37.0531 20.2173C37.173 20.3361 37.2401 20.4962 37.2401 20.6677C37.2401 20.8392 37.174 20.9983 37.0531 21.1181L28.2317 29.9406C28.1119 30.0604 27.9518 30.1265 27.7823 30.1265L27.7803 30.1276Z"
197
+ fill="white"
198
+ />
199
+ </svg>
200
+ </div>
201
+ <div className="pf-chatbot__feedback-complete-text">
202
+ <CardTitle className="pf-chatbot__feedback-complete-title">{title}</CardTitle>
203
+ <CardBody className={`pf-chatbot__feedback-complete-body`}>{body}</CardBody>
204
+ </div>
205
+ </div>
206
+ </Card>
207
+ </div>
208
+ );
209
+ };
210
+
211
+ export default UserFeedbackComplete;
@@ -55,6 +55,8 @@ const AttachButtonBase: React.FunctionComponent<AttachButtonProps> = ({
55
55
  exitDelay={tooltipProps?.exitDelay || 0}
56
56
  distance={tooltipProps?.distance || 8}
57
57
  animationDuration={tooltipProps?.animationDuration || 0}
58
+ // prevents VO announcements of both aria label and tooltip
59
+ aria="none"
58
60
  {...tooltipProps}
59
61
  >
60
62
  <Button
@@ -34,6 +34,8 @@ export const SendButton: React.FunctionComponent<SendButtonProps> = ({
34
34
  exitDelay={tooltipProps?.exitDelay || 0}
35
35
  distance={tooltipProps?.distance || 8}
36
36
  animationDuration={tooltipProps?.animationDuration || 0}
37
+ // prevents VO announcements of both aria label and tooltip
38
+ aria="none"
37
39
  {...tooltipProps}
38
40
  >
39
41
  <Button
@@ -32,6 +32,8 @@ export const StopButton: React.FunctionComponent<StopButtonProps> = ({
32
32
  exitDelay={tooltipProps?.exitDelay || 0}
33
33
  distance={tooltipProps?.distance || 8}
34
34
  animationDuration={tooltipProps?.animationDuration || 0}
35
+ // prevents VO announcements of both aria label and tooltip
36
+ aria="none"
35
37
  {...tooltipProps}
36
38
  >
37
39
  <Button
@@ -22,9 +22,11 @@ export interface ResponseActionButtonProps {
22
22
  tooltipProps?: TooltipProps;
23
23
  /** Whether button is in clicked state */
24
24
  isClicked?: boolean;
25
+ /** Ref applied to button */
26
+ innerRef?: React.Ref<HTMLButtonElement>;
25
27
  }
26
28
 
27
- export const ResponseActionButton: React.FunctionComponent<ResponseActionButtonProps> = ({
29
+ export const ResponseActionButtonBase: React.FunctionComponent<ResponseActionButtonProps> = ({
28
30
  ariaLabel,
29
31
  clickedAriaLabel = ariaLabel,
30
32
  className,
@@ -34,7 +36,9 @@ export const ResponseActionButton: React.FunctionComponent<ResponseActionButtonP
34
36
  tooltipContent,
35
37
  clickedTooltipContent = tooltipContent,
36
38
  tooltipProps,
37
- isClicked = false
39
+ isClicked = false,
40
+ innerRef,
41
+ ...props
38
42
  }) => {
39
43
  const generateAriaLabel = () => {
40
44
  if (ariaLabel) {
@@ -53,6 +57,8 @@ export const ResponseActionButton: React.FunctionComponent<ResponseActionButtonP
53
57
  exitDelay={tooltipProps?.exitDelay || 0}
54
58
  distance={tooltipProps?.distance || 8}
55
59
  animationDuration={tooltipProps?.animationDuration || 0}
60
+ // prevents VO announcements of both aria label and tooltip
61
+ aria="none"
56
62
  {...tooltipProps}
57
63
  >
58
64
  <Button
@@ -67,9 +73,15 @@ export const ResponseActionButton: React.FunctionComponent<ResponseActionButtonP
67
73
  isDisabled={isDisabled}
68
74
  onClick={onClick}
69
75
  size="sm"
76
+ ref={innerRef}
77
+ {...props}
70
78
  ></Button>
71
79
  </Tooltip>
72
80
  );
73
81
  };
74
82
 
83
+ const ResponseActionButton = React.forwardRef((props: ResponseActionButtonProps, ref: React.Ref<HTMLButtonElement>) => (
84
+ <ResponseActionButtonBase innerRef={ref} {...props} />
85
+ ));
86
+
75
87
  export default ResponseActionButton;
@@ -7,9 +7,9 @@ import {
7
7
  OutlinedCopyIcon
8
8
  } from '@patternfly/react-icons';
9
9
  import ResponseActionButton from './ResponseActionButton';
10
- import { TooltipProps } from '@patternfly/react-core';
10
+ import { ButtonProps, TooltipProps } from '@patternfly/react-core';
11
11
 
12
- export interface ActionProps {
12
+ export interface ActionProps extends Omit<ButtonProps, 'ref'> {
13
13
  /** Aria-label for the button */
14
14
  ariaLabel?: string;
15
15
  /** Aria-label for the button, shown when the button is clicked. */
@@ -28,6 +28,12 @@ export interface ActionProps {
28
28
  tooltipProps?: TooltipProps;
29
29
  /** Icon for custom response action */
30
30
  icon?: React.ReactNode;
31
+ /** Ref for response action button */
32
+ ref?: React.Ref<HTMLButtonElement>;
33
+ /** Whether content launched by button, such as the feedback form, is expanded */
34
+ 'aria-expanded'?: boolean;
35
+ /** Id for content controlled by the button, such as the feedback form */
36
+ 'aria-controls'?: string;
31
37
  }
32
38
 
33
39
  export interface ResponseActionProps {
@@ -82,6 +88,9 @@ export const ResponseActions: React.FunctionComponent<ResponseActionProps> = ({
82
88
  tooltipProps={positive.tooltipProps}
83
89
  icon={<OutlinedThumbsUpIcon />}
84
90
  isClicked={activeButton === 'positive'}
91
+ ref={positive.ref}
92
+ aria-expanded={positive['aria-expanded']}
93
+ aria-controls={positive['aria-controls']}
85
94
  ></ResponseActionButton>
86
95
  )}
87
96
  {negative && (
@@ -96,6 +105,9 @@ export const ResponseActions: React.FunctionComponent<ResponseActionProps> = ({
96
105
  tooltipProps={negative.tooltipProps}
97
106
  icon={<OutlinedThumbsDownIcon />}
98
107
  isClicked={activeButton === 'negative'}
108
+ ref={negative.ref}
109
+ aria-expanded={negative['aria-expanded']}
110
+ aria-controls={negative['aria-controls']}
99
111
  ></ResponseActionButton>
100
112
  )}
101
113
  {copy && (
@@ -110,6 +122,9 @@ export const ResponseActions: React.FunctionComponent<ResponseActionProps> = ({
110
122
  tooltipProps={copy.tooltipProps}
111
123
  icon={<OutlinedCopyIcon />}
112
124
  isClicked={activeButton === 'copy'}
125
+ ref={copy.ref}
126
+ aria-expanded={copy['aria-expanded']}
127
+ aria-controls={copy['aria-controls']}
113
128
  ></ResponseActionButton>
114
129
  )}
115
130
  {share && (
@@ -124,6 +139,9 @@ export const ResponseActions: React.FunctionComponent<ResponseActionProps> = ({
124
139
  tooltipProps={share.tooltipProps}
125
140
  icon={<ExternalLinkAltIcon />}
126
141
  isClicked={activeButton === 'share'}
142
+ ref={share.ref}
143
+ aria-expanded={share['aria-expanded']}
144
+ aria-controls={share['aria-controls']}
127
145
  ></ResponseActionButton>
128
146
  )}
129
147
  {listen && (
@@ -138,6 +156,9 @@ export const ResponseActions: React.FunctionComponent<ResponseActionProps> = ({
138
156
  tooltipProps={listen.tooltipProps}
139
157
  icon={<VolumeUpIcon />}
140
158
  isClicked={activeButton === 'listen'}
159
+ ref={listen.ref}
160
+ aria-expanded={listen['aria-expanded']}
161
+ aria-controls={listen['aria-controls']}
141
162
  ></ResponseActionButton>
142
163
  )}
143
164
  {Object.keys(additionalActions).map((action) => (
@@ -153,6 +174,9 @@ export const ResponseActions: React.FunctionComponent<ResponseActionProps> = ({
153
174
  clickedTooltipContent={additionalActions[action]?.clickedTooltipContent}
154
175
  icon={additionalActions[action]?.icon}
155
176
  isClicked={activeButton === action}
177
+ ref={additionalActions[action]?.ref}
178
+ aria-expanded={additionalActions[action]?.['aria-expanded']}
179
+ aria-controls={additionalActions[action]?.['aria-controls']}
156
180
  />
157
181
  ))}
158
182
  </div>
@@ -19,7 +19,7 @@
19
19
  justify-content: space-between;
20
20
  border-bottom: 1px solid var(--pf-t--global--border--color--default);
21
21
  padding: var(--pf-t--global--spacer--lg);
22
- font-weight: 500;
22
+ font-weight: var(--pf-t--global--font--weight--body--bold);
23
23
  }
24
24
 
25
25
  .pf-chatbot__settings-form-row:last-of-type {
@@ -29,6 +29,6 @@
29
29
  .pf-chatbot__settings--title {
30
30
  font-family: var(--pf-t--global--font--family--heading);
31
31
  font-size: var(--pf-t--global--font--size--xl);
32
- font-weight: 500;
32
+ font-weight: var(--pf-t--global--font--weight--body--bold);
33
33
  text-align: center;
34
34
  }
@@ -1,7 +1,7 @@
1
1
  .pf-chatbot__source-details-subhead {
2
2
  color: var(--pf-t--global--text--color--subtle);
3
3
  font-size: var(--pf-t--global--icon--size--font--xs);
4
- font-weight: 500;
4
+ font-weight: var(--pf-t--global--font--weight--body--bold);
5
5
  }
6
6
 
7
7
  .pf-chatbot__source-details-heading {
package/src/main.scss CHANGED
@@ -21,6 +21,7 @@
21
21
  @import './Message/MessageLoading';
22
22
  @import './Message/QuickStarts/QuickStartTile';
23
23
  @import './Message/QuickResponse/QuickResponse';
24
+ @import './Message/UserFeedback/UserFeedback';
24
25
  @import './MessageBar/MessageBar';
25
26
  @import './MessageBox/MessageBox';
26
27
  @import './MessageBox/JumpButton';