@patternfly/chatbot 6.6.0-prerelease.5 → 6.6.0-prerelease.7
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.
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +1 -1
- package/dist/cjs/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +1 -1
- package/dist/cjs/ChatbotConversationHistoryNav/LoadingState.js +1 -1
- package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.js +1 -1
- package/dist/cjs/ChatbotHeader/ChatbotHeaderMenu.test.js +1 -1
- package/dist/cjs/Message/Message.d.ts +4 -0
- package/dist/cjs/Message/Message.js +2 -2
- package/dist/cjs/Message/Message.test.js +72 -0
- package/dist/cjs/ResponseActions/ResponseActions.d.ts +6 -0
- package/dist/cjs/ResponseActions/ResponseActions.js +12 -5
- package/dist/cjs/ResponseActions/ResponseActions.test.js +28 -0
- package/dist/css/main.css +27 -10
- package/dist/css/main.css.map +1 -1
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.d.ts +1 -1
- package/dist/esm/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.js +1 -1
- package/dist/esm/ChatbotConversationHistoryNav/LoadingState.js +1 -1
- package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.js +1 -1
- package/dist/esm/ChatbotHeader/ChatbotHeaderMenu.test.js +1 -1
- package/dist/esm/Message/Message.d.ts +4 -0
- package/dist/esm/Message/Message.js +2 -2
- package/dist/esm/Message/Message.test.js +72 -0
- package/dist/esm/ResponseActions/ResponseActions.d.ts +6 -0
- package/dist/esm/ResponseActions/ResponseActions.js +12 -5
- package/dist/esm/ResponseActions/ResponseActions.test.js +28 -0
- package/package.json +1 -1
- package/patternfly-docs/content/extensions/chatbot/chatbot.md +1 -1
- package/patternfly-docs/content/extensions/chatbot/design-guidelines.md +10 -12
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithResponseActions.tsx +39 -18
- package/patternfly-docs/content/extensions/chatbot/examples/Messages/Messages.md +9 -8
- package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderBasic.tsx +1 -1
- package/patternfly-docs/content/extensions/chatbot/examples/UI/UI.md +16 -16
- package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.md +3 -3
- package/src/Chatbot/Chatbot.scss +8 -1
- package/src/ChatbotContent/ChatbotContent.scss +5 -1
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.test.tsx +1 -1
- package/src/ChatbotConversationHistoryNav/ChatbotConversationHistoryNav.tsx +1 -1
- package/src/ChatbotConversationHistoryNav/LoadingState.tsx +1 -5
- package/src/ChatbotFooter/ChatbotFooter.scss +8 -1
- package/src/ChatbotHeader/ChatbotHeader.scss +5 -2
- package/src/ChatbotHeader/ChatbotHeaderMenu.test.tsx +1 -1
- package/src/ChatbotHeader/ChatbotHeaderMenu.tsx +2 -2
- package/src/Message/Message.scss +12 -0
- package/src/Message/Message.test.tsx +136 -0
- package/src/Message/Message.tsx +12 -1
- package/src/MessageBar/MessageBar.scss +8 -5
- package/src/ResponseActions/ResponseActions.test.tsx +59 -0
- package/src/ResponseActions/ResponseActions.tsx +35 -10
|
@@ -307,7 +307,7 @@ describe('ChatbotConversationHistoryNav', () => {
|
|
|
307
307
|
isLoading
|
|
308
308
|
/>
|
|
309
309
|
);
|
|
310
|
-
expect(screen.getByRole('dialog', { name: /Loading chatbot
|
|
310
|
+
expect(screen.getByRole('dialog', { name: /Loading chatbot chat history/i })).toBeTruthy();
|
|
311
311
|
expect(screen.getByRole('button', { name: /Close drawer panel/i })).toBeTruthy();
|
|
312
312
|
});
|
|
313
313
|
|
|
@@ -84,7 +84,7 @@ export interface ChatbotConversationHistoryNavProps extends DrawerProps {
|
|
|
84
84
|
activeItemId?: string | number;
|
|
85
85
|
/** Callback function for when an item is selected */
|
|
86
86
|
onSelectActiveItem?: (event?: React.MouseEvent, itemId?: string | number) => void;
|
|
87
|
-
/** Items shown in
|
|
87
|
+
/** Items shown in chat history */
|
|
88
88
|
conversations: Conversation[] | { [key: string]: Conversation[] };
|
|
89
89
|
/** Additional button props for new chat button. */
|
|
90
90
|
newChatButtonProps?: ButtonProps;
|
|
@@ -4,11 +4,7 @@ import type { FunctionComponent } from 'react';
|
|
|
4
4
|
export const LoadingState: FunctionComponent<SkeletonProps> = ({ screenreaderText, ...rest }: SkeletonProps) => (
|
|
5
5
|
<div className="pf-chatbot__history-loading">
|
|
6
6
|
<div className="pf-chatbot__history-loading-block">
|
|
7
|
-
<Skeleton
|
|
8
|
-
screenreaderText={screenreaderText ?? 'Loading chatbot conversation history'}
|
|
9
|
-
fontSize="3xl"
|
|
10
|
-
{...rest}
|
|
11
|
-
/>
|
|
7
|
+
<Skeleton screenreaderText={screenreaderText ?? 'Loading chatbot chat history'} fontSize="3xl" {...rest} />
|
|
12
8
|
</div>
|
|
13
9
|
<div className="pf-chatbot__history-loading-block">
|
|
14
10
|
<Skeleton fontSize="sm" width="70%" {...rest} />
|
|
@@ -6,7 +6,10 @@
|
|
|
6
6
|
// ============================================================================
|
|
7
7
|
.pf-chatbot__footer {
|
|
8
8
|
--pf-chatbot__footer--RowGap: var(--pf-t--global--spacer--md);
|
|
9
|
-
background-color: var(
|
|
9
|
+
background-color: var(
|
|
10
|
+
--pf-t--global--background--color--floating--secondary--default,
|
|
11
|
+
--pf-t--global--background--color--secondary--default
|
|
12
|
+
);
|
|
10
13
|
display: flex;
|
|
11
14
|
flex-direction: column;
|
|
12
15
|
row-gap: var(--pf-chatbot__footer--RowGap);
|
|
@@ -36,6 +39,10 @@
|
|
|
36
39
|
display: none;
|
|
37
40
|
}
|
|
38
41
|
}
|
|
42
|
+
.pf-chatbot__footer {
|
|
43
|
+
background-color: var(--pf-t--global--background--color--secondary--default);
|
|
44
|
+
}
|
|
45
|
+
|
|
39
46
|
.pf-chatbot__footer-container {
|
|
40
47
|
width: 90%;
|
|
41
48
|
max-width: 60rem;
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
grid-template-columns: 1fr auto;
|
|
10
10
|
gap: var(--pf-t--global--spacer--sm);
|
|
11
11
|
position: relative; // this is so focus ring on parent chatbot doesn't include header
|
|
12
|
-
background-color: var(
|
|
12
|
+
background-color: var(
|
|
13
|
+
--pf-t--global--background--color--floating--secondary--default,
|
|
14
|
+
--pf-t--global--background--color--secondary--default
|
|
15
|
+
);
|
|
13
16
|
justify-content: space-between;
|
|
14
17
|
padding: var(--pf-t--global--spacer--lg);
|
|
15
18
|
|
|
@@ -76,7 +79,7 @@
|
|
|
76
79
|
.pf-chatbot--drawer,
|
|
77
80
|
.pf-chatbot--docked {
|
|
78
81
|
.pf-chatbot__header {
|
|
79
|
-
background-color: var(--pf-t--global--background--color--secondary--default);
|
|
82
|
+
background-color: var(--pf-t--global--background--color--secondary--floating--default);
|
|
80
83
|
}
|
|
81
84
|
}
|
|
82
85
|
|
|
@@ -12,7 +12,7 @@ describe('ChatbotHeaderMenu', () => {
|
|
|
12
12
|
it('should call onMenuToggle when ChatbotHeaderMenu button is clicked', () => {
|
|
13
13
|
const onMenuToggle = jest.fn();
|
|
14
14
|
render(<ChatbotHeaderMenu className="custom-header-menu" onMenuToggle={onMenuToggle} />);
|
|
15
|
-
fireEvent.click(screen.getByRole('button', { name: 'Chat history
|
|
15
|
+
fireEvent.click(screen.getByRole('button', { name: 'Chat history drawer' }));
|
|
16
16
|
|
|
17
17
|
expect(onMenuToggle).toHaveBeenCalled();
|
|
18
18
|
});
|
|
@@ -25,9 +25,9 @@ const ChatbotHeaderMenuBase: FunctionComponent<ChatbotHeaderMenuProps> = ({
|
|
|
25
25
|
className,
|
|
26
26
|
onMenuToggle,
|
|
27
27
|
tooltipProps,
|
|
28
|
-
menuAriaLabel = 'Chat history
|
|
28
|
+
menuAriaLabel = 'Chat history drawer',
|
|
29
29
|
innerRef,
|
|
30
|
-
tooltipContent = 'Chat history
|
|
30
|
+
tooltipContent = 'Chat history drawer',
|
|
31
31
|
isCompact,
|
|
32
32
|
...props
|
|
33
33
|
}: ChatbotHeaderMenuProps) => {
|
package/src/Message/Message.scss
CHANGED
|
@@ -92,6 +92,18 @@
|
|
|
92
92
|
gap: var(--pf-t--global--spacer--sm);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
.pf-m-visible-interaction {
|
|
96
|
+
opacity: 0;
|
|
97
|
+
transition-timing-function: var(--pf-t--global--motion--timing-function--default);
|
|
98
|
+
transition-duration: var(--pf-t--global--motion--duration--fade--short);
|
|
99
|
+
transition-property: opacity;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
&:hover .pf-m-visible-interaction,
|
|
103
|
+
.pf-m-visible-interaction:focus-within {
|
|
104
|
+
opacity: 1;
|
|
105
|
+
}
|
|
106
|
+
|
|
95
107
|
// targets footnotes specifically
|
|
96
108
|
.footnotes,
|
|
97
109
|
.pf-chatbot__message-text.footnotes {
|
|
@@ -1415,4 +1415,140 @@ describe('Message', () => {
|
|
|
1415
1415
|
expect(screen.getByText('ThumbsUpIcon')).toBeInTheDocument();
|
|
1416
1416
|
expect(screen.queryByText('OutlinedThumbsUpIcon')).not.toBeInTheDocument();
|
|
1417
1417
|
});
|
|
1418
|
+
|
|
1419
|
+
it('should apply pf-m-visible-interaction class to response actions when showActionsOnInteraction is true', () => {
|
|
1420
|
+
render(
|
|
1421
|
+
<Message
|
|
1422
|
+
avatar="./img"
|
|
1423
|
+
role="bot"
|
|
1424
|
+
name="Bot"
|
|
1425
|
+
content="Hi"
|
|
1426
|
+
showActionsOnInteraction
|
|
1427
|
+
actions={{
|
|
1428
|
+
positive: { onClick: jest.fn() }
|
|
1429
|
+
}}
|
|
1430
|
+
/>
|
|
1431
|
+
);
|
|
1432
|
+
|
|
1433
|
+
const responseContainer = screen
|
|
1434
|
+
.getByRole('button', { name: 'Good response' })
|
|
1435
|
+
.closest('.pf-chatbot__response-actions');
|
|
1436
|
+
expect(responseContainer).toHaveClass('pf-m-visible-interaction');
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
it('should not apply pf-m-visible-interaction class to response actions when showActionsOnInteraction is false', () => {
|
|
1440
|
+
render(
|
|
1441
|
+
<Message
|
|
1442
|
+
avatar="./img"
|
|
1443
|
+
role="bot"
|
|
1444
|
+
name="Bot"
|
|
1445
|
+
content="Hi"
|
|
1446
|
+
showActionsOnInteraction={false}
|
|
1447
|
+
actions={{
|
|
1448
|
+
positive: { onClick: jest.fn() }
|
|
1449
|
+
}}
|
|
1450
|
+
/>
|
|
1451
|
+
);
|
|
1452
|
+
|
|
1453
|
+
const responseContainer = screen
|
|
1454
|
+
.getByRole('button', { name: 'Good response' })
|
|
1455
|
+
.closest('.pf-chatbot__response-actions');
|
|
1456
|
+
expect(responseContainer).not.toHaveClass('pf-m-visible-interaction');
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
it('should not apply pf-m-visible-interaction class to response actions by default', () => {
|
|
1460
|
+
render(
|
|
1461
|
+
<Message
|
|
1462
|
+
avatar="./img"
|
|
1463
|
+
role="bot"
|
|
1464
|
+
name="Bot"
|
|
1465
|
+
content="Hi"
|
|
1466
|
+
actions={{
|
|
1467
|
+
positive: { onClick: jest.fn() }
|
|
1468
|
+
}}
|
|
1469
|
+
/>
|
|
1470
|
+
);
|
|
1471
|
+
|
|
1472
|
+
const responseContainer = screen
|
|
1473
|
+
.getByRole('button', { name: 'Good response' })
|
|
1474
|
+
.closest('.pf-chatbot__response-actions');
|
|
1475
|
+
expect(responseContainer).not.toHaveClass('pf-m-visible-interaction');
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
it('should apply pf-m-visible-interaction class to grouped actions container when showActionsOnInteraction is true', () => {
|
|
1479
|
+
render(
|
|
1480
|
+
<Message
|
|
1481
|
+
avatar="./img"
|
|
1482
|
+
role="bot"
|
|
1483
|
+
name="Bot"
|
|
1484
|
+
content="Hi"
|
|
1485
|
+
showActionsOnInteraction
|
|
1486
|
+
actions={[
|
|
1487
|
+
{
|
|
1488
|
+
positive: { onClick: jest.fn() },
|
|
1489
|
+
negative: { onClick: jest.fn() }
|
|
1490
|
+
},
|
|
1491
|
+
{
|
|
1492
|
+
copy: { onClick: jest.fn() }
|
|
1493
|
+
}
|
|
1494
|
+
]}
|
|
1495
|
+
/>
|
|
1496
|
+
);
|
|
1497
|
+
|
|
1498
|
+
const responseContainer = screen
|
|
1499
|
+
.getByRole('button', { name: 'Good response' })
|
|
1500
|
+
.closest('.pf-chatbot__response-actions-groups');
|
|
1501
|
+
expect(responseContainer).toHaveClass('pf-m-visible-interaction');
|
|
1502
|
+
});
|
|
1503
|
+
|
|
1504
|
+
it('should not apply pf-m-visible-interaction class to grouped actions container when showActionsOnInteraction is false', () => {
|
|
1505
|
+
render(
|
|
1506
|
+
<Message
|
|
1507
|
+
avatar="./img"
|
|
1508
|
+
role="bot"
|
|
1509
|
+
name="Bot"
|
|
1510
|
+
content="Hi"
|
|
1511
|
+
showActionsOnInteraction={false}
|
|
1512
|
+
actions={[
|
|
1513
|
+
{
|
|
1514
|
+
positive: { onClick: jest.fn() },
|
|
1515
|
+
negative: { onClick: jest.fn() }
|
|
1516
|
+
},
|
|
1517
|
+
{
|
|
1518
|
+
copy: { onClick: jest.fn() }
|
|
1519
|
+
}
|
|
1520
|
+
]}
|
|
1521
|
+
/>
|
|
1522
|
+
);
|
|
1523
|
+
|
|
1524
|
+
const responseContainer = screen
|
|
1525
|
+
.getByRole('button', { name: 'Good response' })
|
|
1526
|
+
.closest('.pf-chatbot__response-actions-groups');
|
|
1527
|
+
expect(responseContainer).not.toHaveClass('pf-m-visible-interaction');
|
|
1528
|
+
});
|
|
1529
|
+
|
|
1530
|
+
it('should not apply pf-m-visible-interaction class to grouped actions container by default', () => {
|
|
1531
|
+
render(
|
|
1532
|
+
<Message
|
|
1533
|
+
avatar="./img"
|
|
1534
|
+
role="bot"
|
|
1535
|
+
name="Bot"
|
|
1536
|
+
content="Hi"
|
|
1537
|
+
actions={[
|
|
1538
|
+
{
|
|
1539
|
+
positive: { onClick: jest.fn() },
|
|
1540
|
+
negative: { onClick: jest.fn() }
|
|
1541
|
+
},
|
|
1542
|
+
{
|
|
1543
|
+
copy: { onClick: jest.fn() }
|
|
1544
|
+
}
|
|
1545
|
+
]}
|
|
1546
|
+
/>
|
|
1547
|
+
);
|
|
1548
|
+
|
|
1549
|
+
const responseContainer = screen
|
|
1550
|
+
.getByRole('button', { name: 'Good response' })
|
|
1551
|
+
.closest('.pf-chatbot__response-actions-groups');
|
|
1552
|
+
expect(responseContainer).not.toHaveClass('pf-m-visible-interaction');
|
|
1553
|
+
});
|
|
1418
1554
|
});
|
package/src/Message/Message.tsx
CHANGED
|
@@ -114,6 +114,10 @@ export interface MessageProps extends Omit<HTMLProps<HTMLDivElement>, 'role'> {
|
|
|
114
114
|
* For finer control of multiple action groups, use persistActionSelection on each group.
|
|
115
115
|
*/
|
|
116
116
|
persistActionSelection?: boolean;
|
|
117
|
+
/** Flag indicating whether the actions container is only visible when a message is hovered or an action would receive focus. Note
|
|
118
|
+
* that setting this to true will append tooltips inline instead of the document.body.
|
|
119
|
+
*/
|
|
120
|
+
showActionsOnInteraction?: boolean;
|
|
117
121
|
/** Sources for message */
|
|
118
122
|
sources?: SourcesCardProps;
|
|
119
123
|
/** Label for the English word "AI," used to tag messages with role "bot" */
|
|
@@ -214,6 +218,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
|
|
|
214
218
|
isLoading,
|
|
215
219
|
actions,
|
|
216
220
|
persistActionSelection,
|
|
221
|
+
showActionsOnInteraction = false,
|
|
217
222
|
sources,
|
|
218
223
|
botWord = 'AI',
|
|
219
224
|
loadingWord = 'Loading message',
|
|
@@ -382,7 +387,12 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
|
|
|
382
387
|
{!isLoading && !isEditable && actions && (
|
|
383
388
|
<>
|
|
384
389
|
{Array.isArray(actions) ? (
|
|
385
|
-
<div
|
|
390
|
+
<div
|
|
391
|
+
className={css(
|
|
392
|
+
'pf-chatbot__response-actions-groups',
|
|
393
|
+
showActionsOnInteraction && 'pf-m-visible-interaction'
|
|
394
|
+
)}
|
|
395
|
+
>
|
|
386
396
|
{actions.map((actionGroup, index) => (
|
|
387
397
|
<ResponseActions
|
|
388
398
|
key={index}
|
|
@@ -397,6 +407,7 @@ export const MessageBase: FunctionComponent<MessageProps> = ({
|
|
|
397
407
|
actions={actions}
|
|
398
408
|
persistActionSelection={persistActionSelection}
|
|
399
409
|
useFilledIconsOnClick={useFilledIconsOnClick}
|
|
410
|
+
showActionsOnInteraction={showActionsOnInteraction}
|
|
400
411
|
/>
|
|
401
412
|
)}
|
|
402
413
|
</>
|
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
flex-wrap: wrap;
|
|
20
20
|
align-items: center;
|
|
21
21
|
justify-content: flex-end;
|
|
22
|
-
background-color: var(
|
|
22
|
+
background-color: var(
|
|
23
|
+
--pf-t--global--background--color--control--default,
|
|
24
|
+
--pf-t--global--background--color--primary--default
|
|
25
|
+
);
|
|
23
26
|
border-radius: calc(var(--pf-t--global--border--radius--medium) * 2);
|
|
24
27
|
transition: border-color var(--pf-t--global--motion--timing-function--accelerate)
|
|
25
28
|
var(--pf-t--global--motion--duration--sm);
|
|
@@ -42,20 +45,20 @@
|
|
|
42
45
|
|
|
43
46
|
&.pf-m-primary {
|
|
44
47
|
&::after {
|
|
45
|
-
border-color: var(--pf-t--global--border--color--default);
|
|
48
|
+
border-color: var(--pf-t--global--border--color--control--default, --pf-t--global--border--color--default);
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
&:hover {
|
|
50
53
|
&::after {
|
|
51
|
-
border-color: var(--pf-t--global--border--color--default);
|
|
54
|
+
border-color: var(--pf-t--global--border--color--control--default, --pf-t--global--border--color--default);
|
|
52
55
|
border-width: var(--pf-t--global--border--width--control--hover);
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
&:focus-within {
|
|
57
60
|
&::after {
|
|
58
|
-
border-color: var(--pf-t--global--color--
|
|
61
|
+
border-color: var(--pf-t--global--border--color--control--default, --pf-t--global--border--color--default);
|
|
59
62
|
border-width: var(--pf-t--global--border--width--control--clicked);
|
|
60
63
|
}
|
|
61
64
|
}
|
|
@@ -183,7 +186,7 @@
|
|
|
183
186
|
:root:where(.pf-v6-theme-high-contrast) {
|
|
184
187
|
.pf-chatbot__message-bar {
|
|
185
188
|
&::after {
|
|
186
|
-
border-color: var(--pf-t--global--border--color--default);
|
|
189
|
+
border-color: var(--pf-t--global--border--color--control--default, --pf-t--global--border--color--default);
|
|
187
190
|
}
|
|
188
191
|
}
|
|
189
192
|
}
|
|
@@ -437,6 +437,65 @@ describe('ResponseActions', () => {
|
|
|
437
437
|
expect(customBtn).not.toHaveClass('pf-chatbot__button--response-action-clicked');
|
|
438
438
|
});
|
|
439
439
|
|
|
440
|
+
it('should apply pf-m-visible-interaction class when showActionsOnInteraction is true', () => {
|
|
441
|
+
render(
|
|
442
|
+
<ResponseActions
|
|
443
|
+
data-testid="test-id"
|
|
444
|
+
actions={{
|
|
445
|
+
positive: { onClick: jest.fn() },
|
|
446
|
+
negative: { onClick: jest.fn() }
|
|
447
|
+
}}
|
|
448
|
+
showActionsOnInteraction
|
|
449
|
+
/>
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
expect(screen.getByTestId('test-id')).toHaveClass('pf-m-visible-interaction');
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('should not apply pf-m-visible-interaction class when showActionsOnInteraction is false', () => {
|
|
456
|
+
render(
|
|
457
|
+
<ResponseActions
|
|
458
|
+
data-testid="test-id"
|
|
459
|
+
actions={{
|
|
460
|
+
positive: { onClick: jest.fn() },
|
|
461
|
+
negative: { onClick: jest.fn() }
|
|
462
|
+
}}
|
|
463
|
+
showActionsOnInteraction={false}
|
|
464
|
+
/>
|
|
465
|
+
);
|
|
466
|
+
|
|
467
|
+
expect(screen.getByTestId('test-id')).not.toHaveClass('pf-m-visible-interaction');
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it('should not apply pf-m-visible-interaction class by default', () => {
|
|
471
|
+
render(
|
|
472
|
+
<ResponseActions
|
|
473
|
+
data-testid="test-id"
|
|
474
|
+
actions={{
|
|
475
|
+
positive: { onClick: jest.fn() },
|
|
476
|
+
negative: { onClick: jest.fn() }
|
|
477
|
+
}}
|
|
478
|
+
/>
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
expect(screen.getByTestId('test-id')).not.toHaveClass('pf-m-visible-interaction');
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it('should render with custom className', () => {
|
|
485
|
+
render(
|
|
486
|
+
<ResponseActions
|
|
487
|
+
data-testid="test-id"
|
|
488
|
+
actions={{
|
|
489
|
+
positive: { onClick: jest.fn() },
|
|
490
|
+
negative: { onClick: jest.fn() }
|
|
491
|
+
}}
|
|
492
|
+
className="custom-class"
|
|
493
|
+
/>
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
expect(screen.getByTestId('test-id')).toHaveClass('custom-class');
|
|
497
|
+
});
|
|
498
|
+
|
|
440
499
|
describe('icon swapping with useFilledIconsOnClick', () => {
|
|
441
500
|
it('should render outline icons by default', () => {
|
|
442
501
|
render(
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from '@patternfly/react-icons';
|
|
14
14
|
import ResponseActionButton from './ResponseActionButton';
|
|
15
15
|
import { ButtonProps, TooltipProps } from '@patternfly/react-core';
|
|
16
|
+
import { css } from '@patternfly/react-styles';
|
|
16
17
|
|
|
17
18
|
export interface ActionProps extends Omit<ButtonProps, 'ref'> {
|
|
18
19
|
/** Aria-label for the button */
|
|
@@ -51,6 +52,8 @@ type ExtendedActionProps = ActionProps & {
|
|
|
51
52
|
*/
|
|
52
53
|
|
|
53
54
|
export interface ResponseActionProps {
|
|
55
|
+
/** Additional classes for the response actions container. */
|
|
56
|
+
className?: string;
|
|
54
57
|
/** Props for message actions, such as feedback (positive or negative), copy button, share, and listen */
|
|
55
58
|
actions: Record<string, ExtendedActionProps | undefined> & {
|
|
56
59
|
positive?: ActionProps;
|
|
@@ -67,12 +70,19 @@ export interface ResponseActionProps {
|
|
|
67
70
|
/** When true, automatically swaps to filled icon variants when predefined actions are clicked.
|
|
68
71
|
* Predefined actions will use filled variants (e.g., ThumbsUpIcon) when clicked and outline variants (e.g., OutlinedThumbsUpIcon) when not clicked. */
|
|
69
72
|
useFilledIconsOnClick?: boolean;
|
|
73
|
+
/** Flag indicating whether the actions container is only visible when a message is hovered or an action would receive focus. Note
|
|
74
|
+
* that setting this to true will append tooltips inline instead of the document.body.
|
|
75
|
+
*/
|
|
76
|
+
showActionsOnInteraction?: boolean;
|
|
70
77
|
}
|
|
71
78
|
|
|
72
79
|
export const ResponseActions: FunctionComponent<ResponseActionProps> = ({
|
|
80
|
+
className,
|
|
73
81
|
actions,
|
|
74
82
|
persistActionSelection = false,
|
|
75
|
-
useFilledIconsOnClick = false
|
|
83
|
+
useFilledIconsOnClick = false,
|
|
84
|
+
showActionsOnInteraction = false,
|
|
85
|
+
...props
|
|
76
86
|
}) => {
|
|
77
87
|
const [activeButton, setActiveButton] = useState<string>();
|
|
78
88
|
const [clickStatePersisted, setClickStatePersisted] = useState<boolean>(false);
|
|
@@ -173,8 +183,23 @@ export const ResponseActions: FunctionComponent<ResponseActionProps> = ({
|
|
|
173
183
|
return iconMap[actionName].outlined;
|
|
174
184
|
};
|
|
175
185
|
|
|
186
|
+
// We want to append the tooltip inline so that hovering the tooltip keeps the actions container visible
|
|
187
|
+
// when showActionsOnInteraction is true. Otherwise hovering the tooltip causes the actions container
|
|
188
|
+
// to disappear but the tooltip will remain visible.
|
|
189
|
+
const getTooltipContainer = (): HTMLElement => responseActions.current || document.body;
|
|
190
|
+
|
|
191
|
+
const getTooltipProps = (tooltipProps?: TooltipProps) =>
|
|
192
|
+
({
|
|
193
|
+
...(showActionsOnInteraction && { appendTo: getTooltipContainer }),
|
|
194
|
+
...tooltipProps
|
|
195
|
+
}) as TooltipProps;
|
|
196
|
+
|
|
176
197
|
return (
|
|
177
|
-
<div
|
|
198
|
+
<div
|
|
199
|
+
ref={responseActions}
|
|
200
|
+
className={css('pf-chatbot__response-actions', showActionsOnInteraction && 'pf-m-visible-interaction', className)}
|
|
201
|
+
{...props}
|
|
202
|
+
>
|
|
178
203
|
{positive && (
|
|
179
204
|
<ResponseActionButton
|
|
180
205
|
{...positive}
|
|
@@ -185,7 +210,7 @@ export const ResponseActions: FunctionComponent<ResponseActionProps> = ({
|
|
|
185
210
|
isDisabled={positive.isDisabled}
|
|
186
211
|
tooltipContent={positive.tooltipContent ?? 'Good response'}
|
|
187
212
|
clickedTooltipContent={positive.clickedTooltipContent ?? 'Good response recorded'}
|
|
188
|
-
tooltipProps={positive.tooltipProps}
|
|
213
|
+
tooltipProps={getTooltipProps(positive.tooltipProps)}
|
|
189
214
|
icon={getIcon('positive')}
|
|
190
215
|
isClicked={activeButton === 'positive'}
|
|
191
216
|
ref={positive.ref}
|
|
@@ -203,7 +228,7 @@ export const ResponseActions: FunctionComponent<ResponseActionProps> = ({
|
|
|
203
228
|
isDisabled={negative.isDisabled}
|
|
204
229
|
tooltipContent={negative.tooltipContent ?? 'Bad response'}
|
|
205
230
|
clickedTooltipContent={negative.clickedTooltipContent ?? 'Bad response recorded'}
|
|
206
|
-
tooltipProps={negative.tooltipProps}
|
|
231
|
+
tooltipProps={getTooltipProps(negative.tooltipProps)}
|
|
207
232
|
icon={getIcon('negative')}
|
|
208
233
|
isClicked={activeButton === 'negative'}
|
|
209
234
|
ref={negative.ref}
|
|
@@ -221,7 +246,7 @@ export const ResponseActions: FunctionComponent<ResponseActionProps> = ({
|
|
|
221
246
|
isDisabled={copy.isDisabled}
|
|
222
247
|
tooltipContent={copy.tooltipContent ?? 'Copy'}
|
|
223
248
|
clickedTooltipContent={copy.clickedTooltipContent ?? 'Copied'}
|
|
224
|
-
tooltipProps={copy.tooltipProps}
|
|
249
|
+
tooltipProps={getTooltipProps(copy.tooltipProps)}
|
|
225
250
|
icon={<OutlinedCopyIcon />}
|
|
226
251
|
isClicked={activeButton === 'copy'}
|
|
227
252
|
ref={copy.ref}
|
|
@@ -239,7 +264,7 @@ export const ResponseActions: FunctionComponent<ResponseActionProps> = ({
|
|
|
239
264
|
isDisabled={edit.isDisabled}
|
|
240
265
|
tooltipContent={edit.tooltipContent ?? 'Edit '}
|
|
241
266
|
clickedTooltipContent={edit.clickedTooltipContent ?? 'Editing'}
|
|
242
|
-
tooltipProps={edit.tooltipProps}
|
|
267
|
+
tooltipProps={getTooltipProps(edit.tooltipProps)}
|
|
243
268
|
icon={<PencilAltIcon />}
|
|
244
269
|
isClicked={activeButton === 'edit'}
|
|
245
270
|
ref={edit.ref}
|
|
@@ -257,7 +282,7 @@ export const ResponseActions: FunctionComponent<ResponseActionProps> = ({
|
|
|
257
282
|
isDisabled={share.isDisabled}
|
|
258
283
|
tooltipContent={share.tooltipContent ?? 'Share'}
|
|
259
284
|
clickedTooltipContent={share.clickedTooltipContent ?? 'Shared'}
|
|
260
|
-
tooltipProps={share.tooltipProps}
|
|
285
|
+
tooltipProps={getTooltipProps(share.tooltipProps)}
|
|
261
286
|
icon={<ExternalLinkAltIcon />}
|
|
262
287
|
isClicked={activeButton === 'share'}
|
|
263
288
|
ref={share.ref}
|
|
@@ -275,7 +300,7 @@ export const ResponseActions: FunctionComponent<ResponseActionProps> = ({
|
|
|
275
300
|
isDisabled={download.isDisabled}
|
|
276
301
|
tooltipContent={download.tooltipContent ?? 'Download'}
|
|
277
302
|
clickedTooltipContent={download.clickedTooltipContent ?? 'Downloaded'}
|
|
278
|
-
tooltipProps={download.tooltipProps}
|
|
303
|
+
tooltipProps={getTooltipProps(download.tooltipProps)}
|
|
279
304
|
icon={<DownloadIcon />}
|
|
280
305
|
isClicked={activeButton === 'download'}
|
|
281
306
|
ref={download.ref}
|
|
@@ -293,7 +318,7 @@ export const ResponseActions: FunctionComponent<ResponseActionProps> = ({
|
|
|
293
318
|
isDisabled={listen.isDisabled}
|
|
294
319
|
tooltipContent={listen.tooltipContent ?? 'Listen'}
|
|
295
320
|
clickedTooltipContent={listen.clickedTooltipContent ?? 'Listening'}
|
|
296
|
-
tooltipProps={listen.tooltipProps}
|
|
321
|
+
tooltipProps={getTooltipProps(listen.tooltipProps)}
|
|
297
322
|
icon={<VolumeUpIcon />}
|
|
298
323
|
isClicked={activeButton === 'listen'}
|
|
299
324
|
ref={listen.ref}
|
|
@@ -312,7 +337,7 @@ export const ResponseActions: FunctionComponent<ResponseActionProps> = ({
|
|
|
312
337
|
className={additionalActions[action]?.className}
|
|
313
338
|
isDisabled={additionalActions[action]?.isDisabled}
|
|
314
339
|
tooltipContent={additionalActions[action]?.tooltipContent}
|
|
315
|
-
tooltipProps={additionalActions[action]?.tooltipProps}
|
|
340
|
+
tooltipProps={getTooltipProps(additionalActions[action]?.tooltipProps)}
|
|
316
341
|
clickedTooltipContent={additionalActions[action]?.clickedTooltipContent}
|
|
317
342
|
icon={additionalActions[action]?.icon}
|
|
318
343
|
isClicked={activeButton === action}
|