@patternfly/chatbot 6.3.0-prerelease.20 → 6.3.0-prerelease.22

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 (32) hide show
  1. package/dist/cjs/Message/Message.test.js +4 -0
  2. package/dist/cjs/ResponseActions/ResponseActions.d.ts +1 -0
  3. package/dist/cjs/ResponseActions/ResponseActions.js +3 -3
  4. package/dist/cjs/ResponseActions/ResponseActions.test.js +5 -1
  5. package/dist/cjs/SourcesCard/SourcesCard.d.ts +10 -1
  6. package/dist/cjs/SourcesCard/SourcesCard.js +2 -1
  7. package/dist/cjs/SourcesCard/SourcesCard.test.js +10 -0
  8. package/dist/esm/Message/Message.test.js +4 -0
  9. package/dist/esm/ResponseActions/ResponseActions.d.ts +1 -0
  10. package/dist/esm/ResponseActions/ResponseActions.js +4 -4
  11. package/dist/esm/ResponseActions/ResponseActions.test.js +5 -1
  12. package/dist/esm/SourcesCard/SourcesCard.d.ts +10 -1
  13. package/dist/esm/SourcesCard/SourcesCard.js +2 -1
  14. package/dist/esm/SourcesCard/SourcesCard.test.js +10 -0
  15. package/package.json +1 -1
  16. package/patternfly-docs/content/extensions/chatbot/examples/Messages/BotMessage.tsx +0 -8
  17. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithCustomResponseActions.tsx +0 -10
  18. package/patternfly-docs/content/extensions/chatbot/examples/Messages/MessageWithResponseActions.tsx +1 -1
  19. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithActions.tsx +2 -2
  20. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotHeaderDrawerWithSelection.tsx +2 -2
  21. package/patternfly-docs/content/extensions/chatbot/examples/UI/ChatbotWelcomeInteraction.tsx +1 -1
  22. package/patternfly-docs/content/extensions/chatbot/examples/demos/Chatbot.tsx +2 -2
  23. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotCompact.tsx +2 -2
  24. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotInDrawer.tsx +2 -2
  25. package/patternfly-docs/content/extensions/chatbot/examples/demos/ChatbotScrolling.tsx +2 -2
  26. package/patternfly-docs/content/extensions/chatbot/examples/demos/EmbeddedChatbot.tsx +2 -2
  27. package/patternfly-docs/content/extensions/chatbot/examples/demos/EmbeddedComparisonChatbot.tsx +1 -1
  28. package/src/Message/Message.test.tsx +4 -0
  29. package/src/ResponseActions/ResponseActions.test.tsx +5 -1
  30. package/src/ResponseActions/ResponseActions.tsx +22 -2
  31. package/src/SourcesCard/SourcesCard.test.tsx +14 -0
  32. package/src/SourcesCard/SourcesCard.tsx +12 -0
@@ -348,6 +348,8 @@ describe('Message', () => {
348
348
  // eslint-disable-next-line no-console
349
349
  share: { onClick: () => console.log('Share') },
350
350
  // eslint-disable-next-line no-console
351
+ download: { onClick: () => console.log('Download') },
352
+ // eslint-disable-next-line no-console
351
353
  listen: { onClick: () => console.log('Listen') }
352
354
  } }));
353
355
  ALL_ACTIONS.forEach(({ label }) => {
@@ -365,6 +367,8 @@ describe('Message', () => {
365
367
  // eslint-disable-next-line no-console
366
368
  share: { onClick: () => console.log('Share') },
367
369
  // eslint-disable-next-line no-console
370
+ download: { onClick: () => console.log('Download') },
371
+ // eslint-disable-next-line no-console
368
372
  listen: { onClick: () => console.log('Listen') }
369
373
  } }));
370
374
  expect(react_2.screen.getByText('Loading message')).toBeTruthy();
@@ -36,6 +36,7 @@ export interface ResponseActionProps {
36
36
  negative?: ActionProps;
37
37
  copy?: ActionProps;
38
38
  share?: ActionProps;
39
+ download?: ActionProps;
39
40
  listen?: ActionProps;
40
41
  };
41
42
  }
@@ -21,9 +21,9 @@ const react_2 = require("react");
21
21
  const react_icons_1 = require("@patternfly/react-icons");
22
22
  const ResponseActionButton_1 = __importDefault(require("./ResponseActionButton"));
23
23
  const ResponseActions = ({ actions }) => {
24
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
24
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
25
25
  const [activeButton, setActiveButton] = (0, react_2.useState)();
26
- const { positive, negative, copy, share, listen } = actions, additionalActions = __rest(actions, ["positive", "negative", "copy", "share", "listen"]);
26
+ const { positive, negative, copy, share, download, listen } = actions, additionalActions = __rest(actions, ["positive", "negative", "copy", "share", "download", "listen"]);
27
27
  const responseActions = (0, react_2.useRef)(null);
28
28
  (0, react_2.useEffect)(() => {
29
29
  const handleClickOutside = (e) => {
@@ -40,7 +40,7 @@ const ResponseActions = ({ actions }) => {
40
40
  setActiveButton(id);
41
41
  onClick && onClick(e);
42
42
  };
43
- return ((0, jsx_runtime_1.jsxs)("div", { ref: responseActions, className: "pf-chatbot__response-actions", children: [positive && ((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, Object.assign({}, positive, { ariaLabel: (_a = positive.ariaLabel) !== null && _a !== void 0 ? _a : 'Good response', clickedAriaLabel: (_b = positive.ariaLabel) !== null && _b !== void 0 ? _b : 'Response recorded', onClick: (e) => handleClick(e, 'positive', positive.onClick), className: positive.className, isDisabled: positive.isDisabled, tooltipContent: (_c = positive.tooltipContent) !== null && _c !== void 0 ? _c : 'Good response', clickedTooltipContent: (_d = positive.clickedTooltipContent) !== null && _d !== void 0 ? _d : 'Response recorded', tooltipProps: positive.tooltipProps, icon: (0, jsx_runtime_1.jsx)(react_icons_1.OutlinedThumbsUpIcon, {}), isClicked: activeButton === 'positive', ref: positive.ref, "aria-expanded": positive['aria-expanded'], "aria-controls": positive['aria-controls'] }))), negative && ((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, Object.assign({}, negative, { ariaLabel: (_e = negative.ariaLabel) !== null && _e !== void 0 ? _e : 'Bad response', clickedAriaLabel: (_f = negative.ariaLabel) !== null && _f !== void 0 ? _f : 'Response recorded', onClick: (e) => handleClick(e, 'negative', negative.onClick), className: negative.className, isDisabled: negative.isDisabled, tooltipContent: (_g = negative.tooltipContent) !== null && _g !== void 0 ? _g : 'Bad response', clickedTooltipContent: (_h = negative.clickedTooltipContent) !== null && _h !== void 0 ? _h : 'Response recorded', tooltipProps: negative.tooltipProps, icon: (0, jsx_runtime_1.jsx)(react_icons_1.OutlinedThumbsDownIcon, {}), isClicked: activeButton === 'negative', ref: negative.ref, "aria-expanded": negative['aria-expanded'], "aria-controls": negative['aria-controls'] }))), copy && ((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, Object.assign({}, copy, { ariaLabel: (_j = copy.ariaLabel) !== null && _j !== void 0 ? _j : 'Copy', clickedAriaLabel: (_k = copy.ariaLabel) !== null && _k !== void 0 ? _k : 'Copied', onClick: (e) => handleClick(e, 'copy', copy.onClick), className: copy.className, isDisabled: copy.isDisabled, tooltipContent: (_l = copy.tooltipContent) !== null && _l !== void 0 ? _l : 'Copy', clickedTooltipContent: (_m = copy.clickedTooltipContent) !== null && _m !== void 0 ? _m : 'Copied', tooltipProps: copy.tooltipProps, icon: (0, jsx_runtime_1.jsx)(react_icons_1.OutlinedCopyIcon, {}), isClicked: activeButton === 'copy', ref: copy.ref, "aria-expanded": copy['aria-expanded'], "aria-controls": copy['aria-controls'] }))), share && ((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, Object.assign({}, share, { ariaLabel: (_o = share.ariaLabel) !== null && _o !== void 0 ? _o : 'Share', clickedAriaLabel: (_p = share.ariaLabel) !== null && _p !== void 0 ? _p : 'Shared', onClick: (e) => handleClick(e, 'share', share.onClick), className: share.className, isDisabled: share.isDisabled, tooltipContent: (_q = share.tooltipContent) !== null && _q !== void 0 ? _q : 'Share', clickedTooltipContent: (_r = share.clickedTooltipContent) !== null && _r !== void 0 ? _r : 'Shared', tooltipProps: share.tooltipProps, icon: (0, jsx_runtime_1.jsx)(react_icons_1.ExternalLinkAltIcon, {}), isClicked: activeButton === 'share', ref: share.ref, "aria-expanded": share['aria-expanded'], "aria-controls": share['aria-controls'] }))), listen && ((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, Object.assign({}, listen, { ariaLabel: (_s = listen.ariaLabel) !== null && _s !== void 0 ? _s : 'Listen', clickedAriaLabel: (_t = listen.ariaLabel) !== null && _t !== void 0 ? _t : 'Listening', onClick: (e) => handleClick(e, 'listen', listen.onClick), className: listen.className, isDisabled: listen.isDisabled, tooltipContent: (_u = listen.tooltipContent) !== null && _u !== void 0 ? _u : 'Listen', clickedTooltipContent: (_v = listen.clickedTooltipContent) !== null && _v !== void 0 ? _v : 'Listening', tooltipProps: listen.tooltipProps, icon: (0, jsx_runtime_1.jsx)(react_icons_1.VolumeUpIcon, {}), isClicked: activeButton === 'listen', ref: listen.ref, "aria-expanded": listen['aria-expanded'], "aria-controls": listen['aria-controls'] }))), Object.keys(additionalActions).map((action) => {
43
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: responseActions, className: "pf-chatbot__response-actions", children: [positive && ((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, Object.assign({}, positive, { ariaLabel: (_a = positive.ariaLabel) !== null && _a !== void 0 ? _a : 'Good response', clickedAriaLabel: (_b = positive.ariaLabel) !== null && _b !== void 0 ? _b : 'Response recorded', onClick: (e) => handleClick(e, 'positive', positive.onClick), className: positive.className, isDisabled: positive.isDisabled, tooltipContent: (_c = positive.tooltipContent) !== null && _c !== void 0 ? _c : 'Good response', clickedTooltipContent: (_d = positive.clickedTooltipContent) !== null && _d !== void 0 ? _d : 'Response recorded', tooltipProps: positive.tooltipProps, icon: (0, jsx_runtime_1.jsx)(react_icons_1.OutlinedThumbsUpIcon, {}), isClicked: activeButton === 'positive', ref: positive.ref, "aria-expanded": positive['aria-expanded'], "aria-controls": positive['aria-controls'] }))), negative && ((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, Object.assign({}, negative, { ariaLabel: (_e = negative.ariaLabel) !== null && _e !== void 0 ? _e : 'Bad response', clickedAriaLabel: (_f = negative.ariaLabel) !== null && _f !== void 0 ? _f : 'Response recorded', onClick: (e) => handleClick(e, 'negative', negative.onClick), className: negative.className, isDisabled: negative.isDisabled, tooltipContent: (_g = negative.tooltipContent) !== null && _g !== void 0 ? _g : 'Bad response', clickedTooltipContent: (_h = negative.clickedTooltipContent) !== null && _h !== void 0 ? _h : 'Response recorded', tooltipProps: negative.tooltipProps, icon: (0, jsx_runtime_1.jsx)(react_icons_1.OutlinedThumbsDownIcon, {}), isClicked: activeButton === 'negative', ref: negative.ref, "aria-expanded": negative['aria-expanded'], "aria-controls": negative['aria-controls'] }))), copy && ((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, Object.assign({}, copy, { ariaLabel: (_j = copy.ariaLabel) !== null && _j !== void 0 ? _j : 'Copy', clickedAriaLabel: (_k = copy.ariaLabel) !== null && _k !== void 0 ? _k : 'Copied', onClick: (e) => handleClick(e, 'copy', copy.onClick), className: copy.className, isDisabled: copy.isDisabled, tooltipContent: (_l = copy.tooltipContent) !== null && _l !== void 0 ? _l : 'Copy', clickedTooltipContent: (_m = copy.clickedTooltipContent) !== null && _m !== void 0 ? _m : 'Copied', tooltipProps: copy.tooltipProps, icon: (0, jsx_runtime_1.jsx)(react_icons_1.OutlinedCopyIcon, {}), isClicked: activeButton === 'copy', ref: copy.ref, "aria-expanded": copy['aria-expanded'], "aria-controls": copy['aria-controls'] }))), share && ((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, Object.assign({}, share, { ariaLabel: (_o = share.ariaLabel) !== null && _o !== void 0 ? _o : 'Share', clickedAriaLabel: (_p = share.ariaLabel) !== null && _p !== void 0 ? _p : 'Shared', onClick: (e) => handleClick(e, 'share', share.onClick), className: share.className, isDisabled: share.isDisabled, tooltipContent: (_q = share.tooltipContent) !== null && _q !== void 0 ? _q : 'Share', clickedTooltipContent: (_r = share.clickedTooltipContent) !== null && _r !== void 0 ? _r : 'Shared', tooltipProps: share.tooltipProps, icon: (0, jsx_runtime_1.jsx)(react_icons_1.ExternalLinkAltIcon, {}), isClicked: activeButton === 'share', ref: share.ref, "aria-expanded": share['aria-expanded'], "aria-controls": share['aria-controls'] }))), download && ((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, Object.assign({}, download, { ariaLabel: (_s = download.ariaLabel) !== null && _s !== void 0 ? _s : 'Download', clickedAriaLabel: (_t = download.ariaLabel) !== null && _t !== void 0 ? _t : 'Downloaded', onClick: (e) => handleClick(e, 'download', download.onClick), className: download.className, isDisabled: download.isDisabled, tooltipContent: (_u = download.tooltipContent) !== null && _u !== void 0 ? _u : 'Download', clickedTooltipContent: (_v = download.clickedTooltipContent) !== null && _v !== void 0 ? _v : 'Downloaded', tooltipProps: download.tooltipProps, icon: (0, jsx_runtime_1.jsx)(react_icons_1.DownloadIcon, {}), isClicked: activeButton === 'download', ref: download.ref, "aria-expanded": download['aria-expanded'], "aria-controls": download['aria-controls'] }))), listen && ((0, jsx_runtime_1.jsx)(ResponseActionButton_1.default, Object.assign({}, listen, { ariaLabel: (_w = listen.ariaLabel) !== null && _w !== void 0 ? _w : 'Listen', clickedAriaLabel: (_x = listen.ariaLabel) !== null && _x !== void 0 ? _x : 'Listening', onClick: (e) => handleClick(e, 'listen', listen.onClick), className: listen.className, isDisabled: listen.isDisabled, tooltipContent: (_y = listen.tooltipContent) !== null && _y !== void 0 ? _y : 'Listen', clickedTooltipContent: (_z = listen.clickedTooltipContent) !== null && _z !== void 0 ? _z : 'Listening', tooltipProps: listen.tooltipProps, icon: (0, jsx_runtime_1.jsx)(react_icons_1.VolumeUpIcon, {}), isClicked: activeButton === 'listen', ref: listen.ref, "aria-expanded": listen['aria-expanded'], "aria-controls": listen['aria-controls'] }))), Object.keys(additionalActions).map((action) => {
44
44
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
45
45
  return ((0, react_1.createElement)(ResponseActionButton_1.default, Object.assign({}, additionalActions[action], { key: action, ariaLabel: (_a = additionalActions[action]) === null || _a === void 0 ? void 0 : _a.ariaLabel, clickedAriaLabel: (_b = additionalActions[action]) === null || _b === void 0 ? void 0 : _b.clickedAriaLabel, onClick: (e) => { var _a; return handleClick(e, action, (_a = additionalActions[action]) === null || _a === void 0 ? void 0 : _a.onClick); }, className: (_c = additionalActions[action]) === null || _c === void 0 ? void 0 : _c.className, isDisabled: (_d = additionalActions[action]) === null || _d === void 0 ? void 0 : _d.isDisabled, tooltipContent: (_e = additionalActions[action]) === null || _e === void 0 ? void 0 : _e.tooltipContent, tooltipProps: (_f = additionalActions[action]) === null || _f === void 0 ? void 0 : _f.tooltipProps, clickedTooltipContent: (_g = additionalActions[action]) === null || _g === void 0 ? void 0 : _g.clickedTooltipContent, icon: (_h = additionalActions[action]) === null || _h === void 0 ? void 0 : _h.icon, isClicked: activeButton === action, ref: (_j = additionalActions[action]) === null || _j === void 0 ? void 0 : _j.ref, "aria-expanded": (_k = additionalActions[action]) === null || _k === void 0 ? void 0 : _k['aria-expanded'], "aria-controls": (_l = additionalActions[action]) === null || _l === void 0 ? void 0 : _l['aria-controls'] })));
46
46
  })] }));
@@ -57,6 +57,7 @@ const ALL_ACTIONS_DATA_TEST = [
57
57
  { type: 'negative', label: 'Bad response', dataTestId: 'negative' },
58
58
  { type: 'copy', label: 'Copy', dataTestId: 'copy' },
59
59
  { type: 'share', label: 'Share', dataTestId: 'share' },
60
+ { type: 'download', label: 'Download', dataTestId: 'download' },
60
61
  { type: 'listen', label: 'Listen', dataTestId: 'listen' }
61
62
  ];
62
63
  describe('ResponseActions', () => {
@@ -69,14 +70,16 @@ describe('ResponseActions', () => {
69
70
  negative: { onClick: jest.fn() },
70
71
  copy: { onClick: jest.fn() },
71
72
  share: { onClick: jest.fn() },
73
+ download: { onClick: jest.fn() },
72
74
  listen: { onClick: jest.fn() }
73
75
  } }));
74
76
  const goodBtn = react_1.screen.getByRole('button', { name: 'Good response' });
75
77
  const badBtn = react_1.screen.getByRole('button', { name: 'Bad response' });
76
78
  const copyBtn = react_1.screen.getByRole('button', { name: 'Copy' });
77
79
  const shareBtn = react_1.screen.getByRole('button', { name: 'Share' });
80
+ const downloadBtn = react_1.screen.getByRole('button', { name: 'Download' });
78
81
  const listenBtn = react_1.screen.getByRole('button', { name: 'Listen' });
79
- const buttons = [goodBtn, badBtn, copyBtn, shareBtn, listenBtn];
82
+ const buttons = [goodBtn, badBtn, copyBtn, shareBtn, downloadBtn, listenBtn];
80
83
  buttons.forEach((button) => {
81
84
  expect(button).toBeTruthy();
82
85
  });
@@ -149,6 +152,7 @@ describe('ResponseActions', () => {
149
152
  { type: 'negative', ariaLabel: 'Thumbs down' },
150
153
  { type: 'copy', ariaLabel: 'Copy the message' },
151
154
  { type: 'share', ariaLabel: 'Share it with friends' },
155
+ { type: 'download', ariaLabel: 'Download your cool message' },
152
156
  { type: 'listen', ariaLabel: 'Listen up' }
153
157
  ];
154
158
  actions.forEach(({ type, ariaLabel }) => {
@@ -1,5 +1,5 @@
1
1
  import type { FunctionComponent } from 'react';
2
- import { CardProps } from '@patternfly/react-core';
2
+ import { ButtonProps, CardProps } from '@patternfly/react-core';
3
3
  export interface SourcesCardProps extends CardProps {
4
4
  /** Additional classes for the pagination navigation container. */
5
5
  className?: string;
@@ -11,11 +11,20 @@ export interface SourcesCardProps extends CardProps {
11
11
  paginationAriaLabel?: string;
12
12
  /** Content rendered inside the paginated card */
13
13
  sources: {
14
+ /** Title of sources card */
14
15
  title?: string;
16
+ /** Link to source */
15
17
  link: string;
18
+ /** Body of sources card */
16
19
  body?: React.ReactNode | string;
20
+ /** Whether link is external */
17
21
  isExternal?: boolean;
22
+ /** Whether sources card is expandable */
18
23
  hasShowMore?: boolean;
24
+ /** onClick event applied to the title of the Sources card */
25
+ onClick?: React.MouseEventHandler<HTMLButtonElement>;
26
+ /** Any additional props applied to the title of the Sources card */
27
+ titleProps?: ButtonProps;
19
28
  }[];
20
29
  /** Label for the English word "source" */
21
30
  sourceWord?: string;
@@ -17,6 +17,7 @@ const react_1 = require("react");
17
17
  const react_core_1 = require("@patternfly/react-core");
18
18
  const react_icons_1 = require("@patternfly/react-icons");
19
19
  const SourcesCard = (_a) => {
20
+ var _b;
20
21
  var { className, isDisabled, paginationAriaLabel = 'Pagination', sources, sourceWord = 'source', sourceWordPlural = 'sources', toNextPageAriaLabel = 'Go to next page', toPreviousPageAriaLabel = 'Go to previous page', onNextClick, onPreviousClick, onSetPage, showMoreWords = 'show more', showLessWords = 'show less', isCompact } = _a, props = __rest(_a, ["className", "isDisabled", "paginationAriaLabel", "sources", "sourceWord", "sourceWordPlural", "toNextPageAriaLabel", "toPreviousPageAriaLabel", "onNextClick", "onPreviousClick", "onSetPage", "showMoreWords", "showLessWords", "isCompact"]);
21
22
  const [page, setPage] = (0, react_1.useState)(1);
22
23
  const [isExpanded, setIsExpanded] = (0, react_1.useState)(false);
@@ -33,7 +34,7 @@ const SourcesCard = (_a) => {
33
34
  }
34
35
  return `Source ${page}`;
35
36
  };
36
- return ((0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__source", children: [(0, jsx_runtime_1.jsx)("span", { children: (0, react_core_1.pluralize)(sources.length, sourceWord, sourceWordPlural) }), (0, jsx_runtime_1.jsxs)(react_core_1.Card, Object.assign({ isCompact: isCompact, className: "pf-chatbot__sources-card" }, props, { children: [(0, jsx_runtime_1.jsx)(react_core_1.CardTitle, { className: "pf-chatbot__sources-card-title", children: (0, jsx_runtime_1.jsx)(react_core_1.Button, { component: "a", variant: react_core_1.ButtonVariant.link, href: sources[page - 1].link, icon: sources[page - 1].isExternal ? (0, jsx_runtime_1.jsx)(react_icons_1.ExternalLinkSquareAltIcon, {}) : undefined, iconPosition: "end", isInline: true, rel: sources[page - 1].isExternal ? 'noreferrer' : undefined, target: sources[page - 1].isExternal ? '_blank' : undefined, children: renderTitle(sources[page - 1].title) }) }), sources[page - 1].body && ((0, jsx_runtime_1.jsx)(react_core_1.CardBody, { className: `pf-chatbot__sources-card-body`, children: sources[page - 1].hasShowMore ? (
37
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "pf-chatbot__source", children: [(0, jsx_runtime_1.jsx)("span", { children: (0, react_core_1.pluralize)(sources.length, sourceWord, sourceWordPlural) }), (0, jsx_runtime_1.jsxs)(react_core_1.Card, Object.assign({ isCompact: isCompact, className: "pf-chatbot__sources-card" }, props, { children: [(0, jsx_runtime_1.jsx)(react_core_1.CardTitle, { className: "pf-chatbot__sources-card-title", children: (0, jsx_runtime_1.jsx)(react_core_1.Button, Object.assign({ component: "a", variant: react_core_1.ButtonVariant.link, href: sources[page - 1].link, icon: sources[page - 1].isExternal ? (0, jsx_runtime_1.jsx)(react_icons_1.ExternalLinkSquareAltIcon, {}) : undefined, iconPosition: "end", isInline: true, rel: sources[page - 1].isExternal ? 'noreferrer' : undefined, target: sources[page - 1].isExternal ? '_blank' : undefined, onClick: (_b = sources[page - 1].onClick) !== null && _b !== void 0 ? _b : undefined }, sources[page - 1].titleProps, { children: renderTitle(sources[page - 1].title) })) }), sources[page - 1].body && ((0, jsx_runtime_1.jsx)(react_core_1.CardBody, { className: `pf-chatbot__sources-card-body`, children: sources[page - 1].hasShowMore ? (
37
38
  // prevents extra VO announcements of button text - parent Message has aria-live
38
39
  (0, jsx_runtime_1.jsx)("div", { "aria-live": "off", children: (0, jsx_runtime_1.jsx)(react_core_1.ExpandableSection, { variant: react_core_1.ExpandableSectionVariant.truncate, toggleText: isExpanded ? showLessWords : showMoreWords, onToggle: onToggle, isExpanded: isExpanded, truncateMaxLines: 2, children: sources[page - 1].body }) })) : ((0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__sources-card-body-text", children: sources[page - 1].body })) })), sources.length > 1 && ((0, jsx_runtime_1.jsx)(react_core_1.CardFooter, { className: "pf-chatbot__sources-card-footer-container", children: (0, jsx_runtime_1.jsx)("div", { className: "pf-chatbot__sources-card-footer", children: (0, jsx_runtime_1.jsxs)("nav", { className: `pf-chatbot__sources-card-footer-buttons ${className}`, "aria-label": paginationAriaLabel, children: [(0, jsx_runtime_1.jsx)(react_core_1.Button, { variant: react_core_1.ButtonVariant.plain, isDisabled: isDisabled || page === 1, "data-action": "previous", onClick: (event) => {
39
40
  const newPage = page >= 1 ? page - 1 : 1;
@@ -174,4 +174,14 @@ describe('SourcesCard', () => {
174
174
  ] }));
175
175
  expect(react_1.screen.getByRole('region')).toHaveAttribute('class', 'pf-v6-c-expandable-section__content');
176
176
  }));
177
+ it('should call onClick appropriately', () => __awaiter(void 0, void 0, void 0, function* () {
178
+ const spy = jest.fn();
179
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(SourcesCard_1.default, { sources: [{ title: 'How to make an apple pie', link: '', onClick: spy }] }));
180
+ yield user_event_1.default.click(react_1.screen.getByRole('link', { name: /How to make an apple pie/i }));
181
+ expect(spy).toHaveBeenCalled();
182
+ }));
183
+ it('should apply titleProps appropriately', () => {
184
+ (0, react_1.render)((0, jsx_runtime_1.jsx)(SourcesCard_1.default, { sources: [{ title: 'How to make an apple pie', link: '', titleProps: { className: 'test' } }] }));
185
+ expect(react_1.screen.getByRole('link', { name: /How to make an apple pie/i })).toHaveClass('test');
186
+ });
177
187
  });
@@ -343,6 +343,8 @@ describe('Message', () => {
343
343
  // eslint-disable-next-line no-console
344
344
  share: { onClick: () => console.log('Share') },
345
345
  // eslint-disable-next-line no-console
346
+ download: { onClick: () => console.log('Download') },
347
+ // eslint-disable-next-line no-console
346
348
  listen: { onClick: () => console.log('Listen') }
347
349
  } }));
348
350
  ALL_ACTIONS.forEach(({ label }) => {
@@ -360,6 +362,8 @@ describe('Message', () => {
360
362
  // eslint-disable-next-line no-console
361
363
  share: { onClick: () => console.log('Share') },
362
364
  // eslint-disable-next-line no-console
365
+ download: { onClick: () => console.log('Download') },
366
+ // eslint-disable-next-line no-console
363
367
  listen: { onClick: () => console.log('Listen') }
364
368
  } }));
365
369
  expect(screen.getByText('Loading message')).toBeTruthy();
@@ -36,6 +36,7 @@ export interface ResponseActionProps {
36
36
  negative?: ActionProps;
37
37
  copy?: ActionProps;
38
38
  share?: ActionProps;
39
+ download?: ActionProps;
39
40
  listen?: ActionProps;
40
41
  };
41
42
  }
@@ -12,12 +12,12 @@ var __rest = (this && this.__rest) || function (s, e) {
12
12
  import { createElement as _createElement } from "react";
13
13
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
14
  import { useEffect, useRef, useState } from 'react';
15
- import { ExternalLinkAltIcon, VolumeUpIcon, OutlinedThumbsUpIcon, OutlinedThumbsDownIcon, OutlinedCopyIcon } from '@patternfly/react-icons';
15
+ import { ExternalLinkAltIcon, VolumeUpIcon, OutlinedThumbsUpIcon, OutlinedThumbsDownIcon, OutlinedCopyIcon, DownloadIcon } from '@patternfly/react-icons';
16
16
  import ResponseActionButton from './ResponseActionButton';
17
17
  export const ResponseActions = ({ actions }) => {
18
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
18
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z;
19
19
  const [activeButton, setActiveButton] = useState();
20
- const { positive, negative, copy, share, listen } = actions, additionalActions = __rest(actions, ["positive", "negative", "copy", "share", "listen"]);
20
+ const { positive, negative, copy, share, download, listen } = actions, additionalActions = __rest(actions, ["positive", "negative", "copy", "share", "download", "listen"]);
21
21
  const responseActions = useRef(null);
22
22
  useEffect(() => {
23
23
  const handleClickOutside = (e) => {
@@ -34,7 +34,7 @@ export const ResponseActions = ({ actions }) => {
34
34
  setActiveButton(id);
35
35
  onClick && onClick(e);
36
36
  };
37
- return (_jsxs("div", { ref: responseActions, className: "pf-chatbot__response-actions", children: [positive && (_jsx(ResponseActionButton, Object.assign({}, positive, { ariaLabel: (_a = positive.ariaLabel) !== null && _a !== void 0 ? _a : 'Good response', clickedAriaLabel: (_b = positive.ariaLabel) !== null && _b !== void 0 ? _b : 'Response recorded', onClick: (e) => handleClick(e, 'positive', positive.onClick), className: positive.className, isDisabled: positive.isDisabled, tooltipContent: (_c = positive.tooltipContent) !== null && _c !== void 0 ? _c : 'Good response', clickedTooltipContent: (_d = positive.clickedTooltipContent) !== null && _d !== void 0 ? _d : 'Response recorded', tooltipProps: positive.tooltipProps, icon: _jsx(OutlinedThumbsUpIcon, {}), isClicked: activeButton === 'positive', ref: positive.ref, "aria-expanded": positive['aria-expanded'], "aria-controls": positive['aria-controls'] }))), negative && (_jsx(ResponseActionButton, Object.assign({}, negative, { ariaLabel: (_e = negative.ariaLabel) !== null && _e !== void 0 ? _e : 'Bad response', clickedAriaLabel: (_f = negative.ariaLabel) !== null && _f !== void 0 ? _f : 'Response recorded', onClick: (e) => handleClick(e, 'negative', negative.onClick), className: negative.className, isDisabled: negative.isDisabled, tooltipContent: (_g = negative.tooltipContent) !== null && _g !== void 0 ? _g : 'Bad response', clickedTooltipContent: (_h = negative.clickedTooltipContent) !== null && _h !== void 0 ? _h : 'Response recorded', tooltipProps: negative.tooltipProps, icon: _jsx(OutlinedThumbsDownIcon, {}), isClicked: activeButton === 'negative', ref: negative.ref, "aria-expanded": negative['aria-expanded'], "aria-controls": negative['aria-controls'] }))), copy && (_jsx(ResponseActionButton, Object.assign({}, copy, { ariaLabel: (_j = copy.ariaLabel) !== null && _j !== void 0 ? _j : 'Copy', clickedAriaLabel: (_k = copy.ariaLabel) !== null && _k !== void 0 ? _k : 'Copied', onClick: (e) => handleClick(e, 'copy', copy.onClick), className: copy.className, isDisabled: copy.isDisabled, tooltipContent: (_l = copy.tooltipContent) !== null && _l !== void 0 ? _l : 'Copy', clickedTooltipContent: (_m = copy.clickedTooltipContent) !== null && _m !== void 0 ? _m : 'Copied', tooltipProps: copy.tooltipProps, icon: _jsx(OutlinedCopyIcon, {}), isClicked: activeButton === 'copy', ref: copy.ref, "aria-expanded": copy['aria-expanded'], "aria-controls": copy['aria-controls'] }))), share && (_jsx(ResponseActionButton, Object.assign({}, share, { ariaLabel: (_o = share.ariaLabel) !== null && _o !== void 0 ? _o : 'Share', clickedAriaLabel: (_p = share.ariaLabel) !== null && _p !== void 0 ? _p : 'Shared', onClick: (e) => handleClick(e, 'share', share.onClick), className: share.className, isDisabled: share.isDisabled, tooltipContent: (_q = share.tooltipContent) !== null && _q !== void 0 ? _q : 'Share', clickedTooltipContent: (_r = share.clickedTooltipContent) !== null && _r !== void 0 ? _r : 'Shared', tooltipProps: share.tooltipProps, icon: _jsx(ExternalLinkAltIcon, {}), isClicked: activeButton === 'share', ref: share.ref, "aria-expanded": share['aria-expanded'], "aria-controls": share['aria-controls'] }))), listen && (_jsx(ResponseActionButton, Object.assign({}, listen, { ariaLabel: (_s = listen.ariaLabel) !== null && _s !== void 0 ? _s : 'Listen', clickedAriaLabel: (_t = listen.ariaLabel) !== null && _t !== void 0 ? _t : 'Listening', onClick: (e) => handleClick(e, 'listen', listen.onClick), className: listen.className, isDisabled: listen.isDisabled, tooltipContent: (_u = listen.tooltipContent) !== null && _u !== void 0 ? _u : 'Listen', clickedTooltipContent: (_v = listen.clickedTooltipContent) !== null && _v !== void 0 ? _v : 'Listening', tooltipProps: listen.tooltipProps, icon: _jsx(VolumeUpIcon, {}), isClicked: activeButton === 'listen', ref: listen.ref, "aria-expanded": listen['aria-expanded'], "aria-controls": listen['aria-controls'] }))), Object.keys(additionalActions).map((action) => {
37
+ return (_jsxs("div", { ref: responseActions, className: "pf-chatbot__response-actions", children: [positive && (_jsx(ResponseActionButton, Object.assign({}, positive, { ariaLabel: (_a = positive.ariaLabel) !== null && _a !== void 0 ? _a : 'Good response', clickedAriaLabel: (_b = positive.ariaLabel) !== null && _b !== void 0 ? _b : 'Response recorded', onClick: (e) => handleClick(e, 'positive', positive.onClick), className: positive.className, isDisabled: positive.isDisabled, tooltipContent: (_c = positive.tooltipContent) !== null && _c !== void 0 ? _c : 'Good response', clickedTooltipContent: (_d = positive.clickedTooltipContent) !== null && _d !== void 0 ? _d : 'Response recorded', tooltipProps: positive.tooltipProps, icon: _jsx(OutlinedThumbsUpIcon, {}), isClicked: activeButton === 'positive', ref: positive.ref, "aria-expanded": positive['aria-expanded'], "aria-controls": positive['aria-controls'] }))), negative && (_jsx(ResponseActionButton, Object.assign({}, negative, { ariaLabel: (_e = negative.ariaLabel) !== null && _e !== void 0 ? _e : 'Bad response', clickedAriaLabel: (_f = negative.ariaLabel) !== null && _f !== void 0 ? _f : 'Response recorded', onClick: (e) => handleClick(e, 'negative', negative.onClick), className: negative.className, isDisabled: negative.isDisabled, tooltipContent: (_g = negative.tooltipContent) !== null && _g !== void 0 ? _g : 'Bad response', clickedTooltipContent: (_h = negative.clickedTooltipContent) !== null && _h !== void 0 ? _h : 'Response recorded', tooltipProps: negative.tooltipProps, icon: _jsx(OutlinedThumbsDownIcon, {}), isClicked: activeButton === 'negative', ref: negative.ref, "aria-expanded": negative['aria-expanded'], "aria-controls": negative['aria-controls'] }))), copy && (_jsx(ResponseActionButton, Object.assign({}, copy, { ariaLabel: (_j = copy.ariaLabel) !== null && _j !== void 0 ? _j : 'Copy', clickedAriaLabel: (_k = copy.ariaLabel) !== null && _k !== void 0 ? _k : 'Copied', onClick: (e) => handleClick(e, 'copy', copy.onClick), className: copy.className, isDisabled: copy.isDisabled, tooltipContent: (_l = copy.tooltipContent) !== null && _l !== void 0 ? _l : 'Copy', clickedTooltipContent: (_m = copy.clickedTooltipContent) !== null && _m !== void 0 ? _m : 'Copied', tooltipProps: copy.tooltipProps, icon: _jsx(OutlinedCopyIcon, {}), isClicked: activeButton === 'copy', ref: copy.ref, "aria-expanded": copy['aria-expanded'], "aria-controls": copy['aria-controls'] }))), share && (_jsx(ResponseActionButton, Object.assign({}, share, { ariaLabel: (_o = share.ariaLabel) !== null && _o !== void 0 ? _o : 'Share', clickedAriaLabel: (_p = share.ariaLabel) !== null && _p !== void 0 ? _p : 'Shared', onClick: (e) => handleClick(e, 'share', share.onClick), className: share.className, isDisabled: share.isDisabled, tooltipContent: (_q = share.tooltipContent) !== null && _q !== void 0 ? _q : 'Share', clickedTooltipContent: (_r = share.clickedTooltipContent) !== null && _r !== void 0 ? _r : 'Shared', tooltipProps: share.tooltipProps, icon: _jsx(ExternalLinkAltIcon, {}), isClicked: activeButton === 'share', ref: share.ref, "aria-expanded": share['aria-expanded'], "aria-controls": share['aria-controls'] }))), download && (_jsx(ResponseActionButton, Object.assign({}, download, { ariaLabel: (_s = download.ariaLabel) !== null && _s !== void 0 ? _s : 'Download', clickedAriaLabel: (_t = download.ariaLabel) !== null && _t !== void 0 ? _t : 'Downloaded', onClick: (e) => handleClick(e, 'download', download.onClick), className: download.className, isDisabled: download.isDisabled, tooltipContent: (_u = download.tooltipContent) !== null && _u !== void 0 ? _u : 'Download', clickedTooltipContent: (_v = download.clickedTooltipContent) !== null && _v !== void 0 ? _v : 'Downloaded', tooltipProps: download.tooltipProps, icon: _jsx(DownloadIcon, {}), isClicked: activeButton === 'download', ref: download.ref, "aria-expanded": download['aria-expanded'], "aria-controls": download['aria-controls'] }))), listen && (_jsx(ResponseActionButton, Object.assign({}, listen, { ariaLabel: (_w = listen.ariaLabel) !== null && _w !== void 0 ? _w : 'Listen', clickedAriaLabel: (_x = listen.ariaLabel) !== null && _x !== void 0 ? _x : 'Listening', onClick: (e) => handleClick(e, 'listen', listen.onClick), className: listen.className, isDisabled: listen.isDisabled, tooltipContent: (_y = listen.tooltipContent) !== null && _y !== void 0 ? _y : 'Listen', clickedTooltipContent: (_z = listen.clickedTooltipContent) !== null && _z !== void 0 ? _z : 'Listening', tooltipProps: listen.tooltipProps, icon: _jsx(VolumeUpIcon, {}), isClicked: activeButton === 'listen', ref: listen.ref, "aria-expanded": listen['aria-expanded'], "aria-controls": listen['aria-controls'] }))), Object.keys(additionalActions).map((action) => {
38
38
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
39
39
  return (_createElement(ResponseActionButton, Object.assign({}, additionalActions[action], { key: action, ariaLabel: (_a = additionalActions[action]) === null || _a === void 0 ? void 0 : _a.ariaLabel, clickedAriaLabel: (_b = additionalActions[action]) === null || _b === void 0 ? void 0 : _b.clickedAriaLabel, onClick: (e) => { var _a; return handleClick(e, action, (_a = additionalActions[action]) === null || _a === void 0 ? void 0 : _a.onClick); }, className: (_c = additionalActions[action]) === null || _c === void 0 ? void 0 : _c.className, isDisabled: (_d = additionalActions[action]) === null || _d === void 0 ? void 0 : _d.isDisabled, tooltipContent: (_e = additionalActions[action]) === null || _e === void 0 ? void 0 : _e.tooltipContent, tooltipProps: (_f = additionalActions[action]) === null || _f === void 0 ? void 0 : _f.tooltipProps, clickedTooltipContent: (_g = additionalActions[action]) === null || _g === void 0 ? void 0 : _g.clickedTooltipContent, icon: (_h = additionalActions[action]) === null || _h === void 0 ? void 0 : _h.icon, isClicked: activeButton === action, ref: (_j = additionalActions[action]) === null || _j === void 0 ? void 0 : _j.ref, "aria-expanded": (_k = additionalActions[action]) === null || _k === void 0 ? void 0 : _k['aria-expanded'], "aria-controls": (_l = additionalActions[action]) === null || _l === void 0 ? void 0 : _l['aria-controls'] })));
40
40
  })] }));
@@ -52,6 +52,7 @@ const ALL_ACTIONS_DATA_TEST = [
52
52
  { type: 'negative', label: 'Bad response', dataTestId: 'negative' },
53
53
  { type: 'copy', label: 'Copy', dataTestId: 'copy' },
54
54
  { type: 'share', label: 'Share', dataTestId: 'share' },
55
+ { type: 'download', label: 'Download', dataTestId: 'download' },
55
56
  { type: 'listen', label: 'Listen', dataTestId: 'listen' }
56
57
  ];
57
58
  describe('ResponseActions', () => {
@@ -64,14 +65,16 @@ describe('ResponseActions', () => {
64
65
  negative: { onClick: jest.fn() },
65
66
  copy: { onClick: jest.fn() },
66
67
  share: { onClick: jest.fn() },
68
+ download: { onClick: jest.fn() },
67
69
  listen: { onClick: jest.fn() }
68
70
  } }));
69
71
  const goodBtn = screen.getByRole('button', { name: 'Good response' });
70
72
  const badBtn = screen.getByRole('button', { name: 'Bad response' });
71
73
  const copyBtn = screen.getByRole('button', { name: 'Copy' });
72
74
  const shareBtn = screen.getByRole('button', { name: 'Share' });
75
+ const downloadBtn = screen.getByRole('button', { name: 'Download' });
73
76
  const listenBtn = screen.getByRole('button', { name: 'Listen' });
74
- const buttons = [goodBtn, badBtn, copyBtn, shareBtn, listenBtn];
77
+ const buttons = [goodBtn, badBtn, copyBtn, shareBtn, downloadBtn, listenBtn];
75
78
  buttons.forEach((button) => {
76
79
  expect(button).toBeTruthy();
77
80
  });
@@ -144,6 +147,7 @@ describe('ResponseActions', () => {
144
147
  { type: 'negative', ariaLabel: 'Thumbs down' },
145
148
  { type: 'copy', ariaLabel: 'Copy the message' },
146
149
  { type: 'share', ariaLabel: 'Share it with friends' },
150
+ { type: 'download', ariaLabel: 'Download your cool message' },
147
151
  { type: 'listen', ariaLabel: 'Listen up' }
148
152
  ];
149
153
  actions.forEach(({ type, ariaLabel }) => {
@@ -1,5 +1,5 @@
1
1
  import type { FunctionComponent } from 'react';
2
- import { CardProps } from '@patternfly/react-core';
2
+ import { ButtonProps, CardProps } from '@patternfly/react-core';
3
3
  export interface SourcesCardProps extends CardProps {
4
4
  /** Additional classes for the pagination navigation container. */
5
5
  className?: string;
@@ -11,11 +11,20 @@ export interface SourcesCardProps extends CardProps {
11
11
  paginationAriaLabel?: string;
12
12
  /** Content rendered inside the paginated card */
13
13
  sources: {
14
+ /** Title of sources card */
14
15
  title?: string;
16
+ /** Link to source */
15
17
  link: string;
18
+ /** Body of sources card */
16
19
  body?: React.ReactNode | string;
20
+ /** Whether link is external */
17
21
  isExternal?: boolean;
22
+ /** Whether sources card is expandable */
18
23
  hasShowMore?: boolean;
24
+ /** onClick event applied to the title of the Sources card */
25
+ onClick?: React.MouseEventHandler<HTMLButtonElement>;
26
+ /** Any additional props applied to the title of the Sources card */
27
+ titleProps?: ButtonProps;
19
28
  }[];
20
29
  /** Label for the English word "source" */
21
30
  sourceWord?: string;
@@ -15,6 +15,7 @@ import { useState } from 'react';
15
15
  import { Button, ButtonVariant, Card, CardBody, CardFooter, CardTitle, ExpandableSection, ExpandableSectionVariant, Icon, pluralize, Truncate } from '@patternfly/react-core';
16
16
  import { ExternalLinkSquareAltIcon } from '@patternfly/react-icons';
17
17
  const SourcesCard = (_a) => {
18
+ var _b;
18
19
  var { className, isDisabled, paginationAriaLabel = 'Pagination', sources, sourceWord = 'source', sourceWordPlural = 'sources', toNextPageAriaLabel = 'Go to next page', toPreviousPageAriaLabel = 'Go to previous page', onNextClick, onPreviousClick, onSetPage, showMoreWords = 'show more', showLessWords = 'show less', isCompact } = _a, props = __rest(_a, ["className", "isDisabled", "paginationAriaLabel", "sources", "sourceWord", "sourceWordPlural", "toNextPageAriaLabel", "toPreviousPageAriaLabel", "onNextClick", "onPreviousClick", "onSetPage", "showMoreWords", "showLessWords", "isCompact"]);
19
20
  const [page, setPage] = useState(1);
20
21
  const [isExpanded, setIsExpanded] = useState(false);
@@ -31,7 +32,7 @@ const SourcesCard = (_a) => {
31
32
  }
32
33
  return `Source ${page}`;
33
34
  };
34
- return (_jsxs("div", { className: "pf-chatbot__source", children: [_jsx("span", { children: pluralize(sources.length, sourceWord, sourceWordPlural) }), _jsxs(Card, Object.assign({ isCompact: isCompact, className: "pf-chatbot__sources-card" }, props, { children: [_jsx(CardTitle, { className: "pf-chatbot__sources-card-title", children: _jsx(Button, { component: "a", variant: ButtonVariant.link, href: sources[page - 1].link, icon: sources[page - 1].isExternal ? _jsx(ExternalLinkSquareAltIcon, {}) : undefined, iconPosition: "end", isInline: true, rel: sources[page - 1].isExternal ? 'noreferrer' : undefined, target: sources[page - 1].isExternal ? '_blank' : undefined, children: renderTitle(sources[page - 1].title) }) }), sources[page - 1].body && (_jsx(CardBody, { className: `pf-chatbot__sources-card-body`, children: sources[page - 1].hasShowMore ? (
35
+ return (_jsxs("div", { className: "pf-chatbot__source", children: [_jsx("span", { children: pluralize(sources.length, sourceWord, sourceWordPlural) }), _jsxs(Card, Object.assign({ isCompact: isCompact, className: "pf-chatbot__sources-card" }, props, { children: [_jsx(CardTitle, { className: "pf-chatbot__sources-card-title", children: _jsx(Button, Object.assign({ component: "a", variant: ButtonVariant.link, href: sources[page - 1].link, icon: sources[page - 1].isExternal ? _jsx(ExternalLinkSquareAltIcon, {}) : undefined, iconPosition: "end", isInline: true, rel: sources[page - 1].isExternal ? 'noreferrer' : undefined, target: sources[page - 1].isExternal ? '_blank' : undefined, onClick: (_b = sources[page - 1].onClick) !== null && _b !== void 0 ? _b : undefined }, sources[page - 1].titleProps, { children: renderTitle(sources[page - 1].title) })) }), sources[page - 1].body && (_jsx(CardBody, { className: `pf-chatbot__sources-card-body`, children: sources[page - 1].hasShowMore ? (
35
36
  // prevents extra VO announcements of button text - parent Message has aria-live
36
37
  _jsx("div", { "aria-live": "off", children: _jsx(ExpandableSection, { variant: ExpandableSectionVariant.truncate, toggleText: isExpanded ? showLessWords : showMoreWords, onToggle: onToggle, isExpanded: isExpanded, truncateMaxLines: 2, children: sources[page - 1].body }) })) : (_jsx("div", { className: "pf-chatbot__sources-card-body-text", children: sources[page - 1].body })) })), sources.length > 1 && (_jsx(CardFooter, { className: "pf-chatbot__sources-card-footer-container", children: _jsx("div", { className: "pf-chatbot__sources-card-footer", children: _jsxs("nav", { className: `pf-chatbot__sources-card-footer-buttons ${className}`, "aria-label": paginationAriaLabel, children: [_jsx(Button, { variant: ButtonVariant.plain, isDisabled: isDisabled || page === 1, "data-action": "previous", onClick: (event) => {
37
38
  const newPage = page >= 1 ? page - 1 : 1;
@@ -169,4 +169,14 @@ describe('SourcesCard', () => {
169
169
  ] }));
170
170
  expect(screen.getByRole('region')).toHaveAttribute('class', 'pf-v6-c-expandable-section__content');
171
171
  }));
172
+ it('should call onClick appropriately', () => __awaiter(void 0, void 0, void 0, function* () {
173
+ const spy = jest.fn();
174
+ render(_jsx(SourcesCard, { sources: [{ title: 'How to make an apple pie', link: '', onClick: spy }] }));
175
+ yield userEvent.click(screen.getByRole('link', { name: /How to make an apple pie/i }));
176
+ expect(spy).toHaveBeenCalled();
177
+ }));
178
+ it('should apply titleProps appropriately', () => {
179
+ render(_jsx(SourcesCard, { sources: [{ title: 'How to make an apple pie', link: '', titleProps: { className: 'test' } }] }));
180
+ expect(screen.getByRole('link', { name: /How to make an apple pie/i })).toHaveClass('test');
181
+ });
172
182
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/chatbot",
3
- "version": "6.3.0-prerelease.20",
3
+ "version": "6.3.0-prerelease.22",
4
4
  "description": "This library provides React components based on PatternFly 6 that can be used to build chatbots.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -225,14 +225,6 @@ _Italic text, formatted with single underscores_
225
225
  content="This bot has a square avatar. You can further customize the avatar by applying an additional class or passing [PatternFly avatar props](/components/avatar) to the `<Message>` component via `avatarProps`."
226
226
  hasRoundAvatar={false}
227
227
  />
228
- <Message
229
- name="Bot"
230
- role="bot"
231
- avatar={patternflyAvatar}
232
- content={`Text-based message from a bot named "Bot," with updated timestamp`}
233
- timestamp="1 hour ago"
234
- />
235
- <Message name="Bot" role="bot" avatar={patternflyAvatar} content="Example content" isLoading />
236
228
  <Select
237
229
  id="single-select"
238
230
  isOpen={isOpen}
@@ -3,7 +3,6 @@ import { FunctionComponent } from 'react';
3
3
  import Message from '@patternfly/chatbot/dist/dynamic/Message';
4
4
  import patternflyAvatar from './patternfly_avatar.jpg';
5
5
  import InfoCircleIcon from '@patternfly/react-icons/dist/esm/icons/info-circle-icon';
6
- import DownloadIcon from '@patternfly/react-icons/dist/esm/icons/download-icon';
7
6
  import RedoIcon from '@patternfly/react-icons/dist/esm/icons/redo-icon';
8
7
 
9
8
  export const CustomActionExample: FunctionComponent = () => (
@@ -22,15 +21,6 @@ export const CustomActionExample: FunctionComponent = () => (
22
21
  clickedTooltipContent: 'Regenerated',
23
22
  icon: <RedoIcon />
24
23
  },
25
- download: {
26
- ariaLabel: 'Download',
27
- clickedAriaLabel: 'Downloaded',
28
- // eslint-disable-next-line no-console
29
- onClick: () => console.log('Clicked download'),
30
- tooltipContent: 'Download',
31
- clickedTooltipContent: 'Downloaded',
32
- icon: <DownloadIcon />
33
- },
34
24
  info: {
35
25
  ariaLabel: 'Info',
36
26
  // eslint-disable-next-line no-console
@@ -17,7 +17,7 @@ export const ResponseActionExample: FunctionComponent = () => (
17
17
  // eslint-disable-next-line no-console
18
18
  copy: { onClick: () => console.log('Copy') },
19
19
  // eslint-disable-next-line no-console
20
- share: { onClick: () => console.log('Share') },
20
+ download: { onClick: () => console.log('Download') },
21
21
  // eslint-disable-next-line no-console
22
22
  listen: { onClick: () => console.log('Listen') }
23
23
  }}
@@ -7,8 +7,8 @@ import { Checkbox, DropdownItem, DropdownList } from '@patternfly/react-core';
7
7
 
8
8
  const menuItems = [
9
9
  <DropdownList key="list-1">
10
- <DropdownItem value="Share" id="Share">
11
- Share
10
+ <DropdownItem value="Download" id="Download">
11
+ Download
12
12
  </DropdownItem>
13
13
  <DropdownItem value="Rename" id="Rename">
14
14
  Rename
@@ -7,8 +7,8 @@ import { Checkbox, DropdownItem, DropdownList } from '@patternfly/react-core';
7
7
 
8
8
  const menuItems = [
9
9
  <DropdownList key="list-1">
10
- <DropdownItem value="Share" id="Share">
11
- Share
10
+ <DropdownItem value="Download" id="Download">
11
+ Download
12
12
  </DropdownItem>
13
13
  <DropdownItem value="Rename" id="Rename">
14
14
  Rename
@@ -80,7 +80,7 @@ export const ChatbotWelcomeInteractionDemo: FunctionComponent = () => {
80
80
  // eslint-disable-next-line no-console
81
81
  copy: { onClick: () => console.log('Copy') },
82
82
  // eslint-disable-next-line no-console
83
- share: { onClick: () => console.log('Share') },
83
+ download: { onClick: () => console.log('Download') },
84
84
  // eslint-disable-next-line no-console
85
85
  listen: { onClick: () => console.log('Listen') }
86
86
  }
@@ -132,7 +132,7 @@ const initialMessages: MessageProps[] = [
132
132
  positive: { onClick: () => tracking.trackSingleItem(actionEventName, { response: 'Good response' }) },
133
133
  negative: { onClick: () => tracking.trackSingleItem(actionEventName, { response: 'Bad response' }) },
134
134
  copy: { onClick: () => tracking.trackSingleItem(actionEventName, { response: 'Copy' }) },
135
- share: { onClick: () => tracking.trackSingleItem(actionEventName, { response: 'Share' }) },
135
+ download: { onClick: () => tracking.trackSingleItem(actionEventName, { response: 'Download' }) },
136
136
  listen: { onClick: () => tracking.trackSingleItem(actionEventName, { response: 'Listen' }) }
137
137
  }
138
138
  }
@@ -269,7 +269,7 @@ export const ChatbotDemo: FunctionComponent = () => {
269
269
  positive: { onClick: () => tracking.trackSingleItem(actionEvent2, { response: 'Good response' }) },
270
270
  negative: { onClick: () => tracking.trackSingleItem(actionEvent2, { response: 'Bad response' }) },
271
271
  copy: { onClick: () => tracking.trackSingleItem(actionEvent2, { response: 'Copy' }) },
272
- share: { onClick: () => tracking.trackSingleItem(actionEvent2, { response: 'Share' }) },
272
+ download: { onClick: () => tracking.trackSingleItem(actionEvent2, { response: 'Download' }) },
273
273
  listen: { onClick: () => tracking.trackSingleItem(actionEvent2, { response: 'Listen' }) }
274
274
  }
275
275
  });
@@ -117,7 +117,7 @@ const initialMessages: MessageProps[] = [
117
117
  // eslint-disable-next-line no-console
118
118
  copy: { onClick: () => console.log('Copy') },
119
119
  // eslint-disable-next-line no-console
120
- share: { onClick: () => console.log('Share') },
120
+ download: { onClick: () => console.log('Download') },
121
121
  // eslint-disable-next-line no-console
122
122
  listen: { onClick: () => console.log('Listen') }
123
123
  }
@@ -256,7 +256,7 @@ export const ChatbotDemo: FunctionComponent = () => {
256
256
  // eslint-disable-next-line no-console
257
257
  copy: { onClick: () => console.log('Copy') },
258
258
  // eslint-disable-next-line no-console
259
- share: { onClick: () => console.log('Share') },
259
+ download: { onClick: () => console.log('Download') },
260
260
  // eslint-disable-next-line no-console
261
261
  listen: { onClick: () => console.log('Listen') }
262
262
  }
@@ -130,7 +130,7 @@ const initialMessages: MessageProps[] = [
130
130
  // eslint-disable-next-line no-console
131
131
  copy: { onClick: () => console.log('Copy') },
132
132
  // eslint-disable-next-line no-console
133
- share: { onClick: () => console.log('Share') },
133
+ download: { onClick: () => console.log('Download') },
134
134
  // eslint-disable-next-line no-console
135
135
  listen: { onClick: () => console.log('Listen') }
136
136
  }
@@ -266,7 +266,7 @@ export const EmbeddedChatbotDemo: FunctionComponent = () => {
266
266
  // eslint-disable-next-line no-console
267
267
  copy: { onClick: () => console.log('Copy') },
268
268
  // eslint-disable-next-line no-console
269
- share: { onClick: () => console.log('Share') },
269
+ download: { onClick: () => console.log('Download') },
270
270
  // eslint-disable-next-line no-console
271
271
  listen: { onClick: () => console.log('Listen') }
272
272
  },
@@ -122,7 +122,7 @@ const initialMessages: MessageProps[] = [
122
122
  // eslint-disable-next-line no-console
123
123
  copy: { onClick: () => console.log('Copy') },
124
124
  // eslint-disable-next-line no-console
125
- share: { onClick: () => console.log('Share') },
125
+ download: { onClick: () => console.log('Download') },
126
126
  // eslint-disable-next-line no-console
127
127
  listen: { onClick: () => console.log('Listen') }
128
128
  }
@@ -283,7 +283,7 @@ export const ChatbotScrollingDemo: React.FunctionComponent = () => {
283
283
  // eslint-disable-next-line no-console
284
284
  copy: { onClick: () => console.log('Copy') },
285
285
  // eslint-disable-next-line no-console
286
- share: { onClick: () => console.log('Share') },
286
+ download: { onClick: () => console.log('Download') },
287
287
  // eslint-disable-next-line no-console
288
288
  listen: { onClick: () => console.log('Listen') }
289
289
  }
@@ -128,7 +128,7 @@ const initialMessages: MessageProps[] = [
128
128
  // eslint-disable-next-line no-console
129
129
  copy: { onClick: () => console.log('Copy') },
130
130
  // eslint-disable-next-line no-console
131
- share: { onClick: () => console.log('Share') },
131
+ download: { onClick: () => console.log('Download') },
132
132
  // eslint-disable-next-line no-console
133
133
  listen: { onClick: () => console.log('Listen') }
134
134
  }
@@ -257,7 +257,7 @@ export const EmbeddedChatbotDemo: FunctionComponent = () => {
257
257
  // eslint-disable-next-line no-console
258
258
  copy: { onClick: () => console.log('Copy') },
259
259
  // eslint-disable-next-line no-console
260
- share: { onClick: () => console.log('Share') },
260
+ download: { onClick: () => console.log('Download') },
261
261
  // eslint-disable-next-line no-console
262
262
  listen: { onClick: () => console.log('Listen') }
263
263
  },
@@ -86,7 +86,7 @@ export const CompareChild = ({ name, input, hasNewInput, setIsSendButtonDisabled
86
86
  // eslint-disable-next-line no-console
87
87
  copy: { onClick: () => console.log('Copy') },
88
88
  // eslint-disable-next-line no-console
89
- share: { onClick: () => console.log('Share') },
89
+ download: { onClick: () => console.log('Download') },
90
90
  // eslint-disable-next-line no-console
91
91
  listen: { onClick: () => console.log('Listen') }
92
92
  },
@@ -428,6 +428,8 @@ describe('Message', () => {
428
428
  // eslint-disable-next-line no-console
429
429
  share: { onClick: () => console.log('Share') },
430
430
  // eslint-disable-next-line no-console
431
+ download: { onClick: () => console.log('Download') },
432
+ // eslint-disable-next-line no-console
431
433
  listen: { onClick: () => console.log('Listen') }
432
434
  }}
433
435
  />
@@ -454,6 +456,8 @@ describe('Message', () => {
454
456
  // eslint-disable-next-line no-console
455
457
  share: { onClick: () => console.log('Share') },
456
458
  // eslint-disable-next-line no-console
459
+ download: { onClick: () => console.log('Download') },
460
+ // eslint-disable-next-line no-console
457
461
  listen: { onClick: () => console.log('Listen') }
458
462
  }}
459
463
  />
@@ -45,6 +45,7 @@ const ALL_ACTIONS_DATA_TEST = [
45
45
  { type: 'negative', label: 'Bad response', dataTestId: 'negative' },
46
46
  { type: 'copy', label: 'Copy', dataTestId: 'copy' },
47
47
  { type: 'share', label: 'Share', dataTestId: 'share' },
48
+ { type: 'download', label: 'Download', dataTestId: 'download' },
48
49
  { type: 'listen', label: 'Listen', dataTestId: 'listen' }
49
50
  ];
50
51
 
@@ -60,6 +61,7 @@ describe('ResponseActions', () => {
60
61
  negative: { onClick: jest.fn() },
61
62
  copy: { onClick: jest.fn() },
62
63
  share: { onClick: jest.fn() },
64
+ download: { onClick: jest.fn() },
63
65
  listen: { onClick: jest.fn() }
64
66
  }}
65
67
  />
@@ -68,8 +70,9 @@ describe('ResponseActions', () => {
68
70
  const badBtn = screen.getByRole('button', { name: 'Bad response' });
69
71
  const copyBtn = screen.getByRole('button', { name: 'Copy' });
70
72
  const shareBtn = screen.getByRole('button', { name: 'Share' });
73
+ const downloadBtn = screen.getByRole('button', { name: 'Download' });
71
74
  const listenBtn = screen.getByRole('button', { name: 'Listen' });
72
- const buttons = [goodBtn, badBtn, copyBtn, shareBtn, listenBtn];
75
+ const buttons = [goodBtn, badBtn, copyBtn, shareBtn, downloadBtn, listenBtn];
73
76
  buttons.forEach((button) => {
74
77
  expect(button).toBeTruthy();
75
78
  });
@@ -166,6 +169,7 @@ describe('ResponseActions', () => {
166
169
  { type: 'negative', ariaLabel: 'Thumbs down' },
167
170
  { type: 'copy', ariaLabel: 'Copy the message' },
168
171
  { type: 'share', ariaLabel: 'Share it with friends' },
172
+ { type: 'download', ariaLabel: 'Download your cool message' },
169
173
  { type: 'listen', ariaLabel: 'Listen up' }
170
174
  ];
171
175
  actions.forEach(({ type, ariaLabel }) => {
@@ -5,7 +5,8 @@ import {
5
5
  VolumeUpIcon,
6
6
  OutlinedThumbsUpIcon,
7
7
  OutlinedThumbsDownIcon,
8
- OutlinedCopyIcon
8
+ OutlinedCopyIcon,
9
+ DownloadIcon
9
10
  } from '@patternfly/react-icons';
10
11
  import ResponseActionButton from './ResponseActionButton';
11
12
  import { ButtonProps, TooltipProps } from '@patternfly/react-core';
@@ -47,13 +48,14 @@ export interface ResponseActionProps {
47
48
  negative?: ActionProps;
48
49
  copy?: ActionProps;
49
50
  share?: ActionProps;
51
+ download?: ActionProps;
50
52
  listen?: ActionProps;
51
53
  };
52
54
  }
53
55
 
54
56
  export const ResponseActions: FunctionComponent<ResponseActionProps> = ({ actions }) => {
55
57
  const [activeButton, setActiveButton] = useState<string>();
56
- const { positive, negative, copy, share, listen, ...additionalActions } = actions;
58
+ const { positive, negative, copy, share, download, listen, ...additionalActions } = actions;
57
59
  const responseActions = useRef<HTMLDivElement>(null);
58
60
 
59
61
  useEffect(() => {
@@ -152,6 +154,24 @@ export const ResponseActions: FunctionComponent<ResponseActionProps> = ({ action
152
154
  aria-controls={share['aria-controls']}
153
155
  ></ResponseActionButton>
154
156
  )}
157
+ {download && (
158
+ <ResponseActionButton
159
+ {...download}
160
+ ariaLabel={download.ariaLabel ?? 'Download'}
161
+ clickedAriaLabel={download.ariaLabel ?? 'Downloaded'}
162
+ onClick={(e) => handleClick(e, 'download', download.onClick)}
163
+ className={download.className}
164
+ isDisabled={download.isDisabled}
165
+ tooltipContent={download.tooltipContent ?? 'Download'}
166
+ clickedTooltipContent={download.clickedTooltipContent ?? 'Downloaded'}
167
+ tooltipProps={download.tooltipProps}
168
+ icon={<DownloadIcon />}
169
+ isClicked={activeButton === 'download'}
170
+ ref={download.ref}
171
+ aria-expanded={download['aria-expanded']}
172
+ aria-controls={download['aria-controls']}
173
+ ></ResponseActionButton>
174
+ )}
155
175
  {listen && (
156
176
  <ResponseActionButton
157
177
  {...listen}
@@ -242,4 +242,18 @@ describe('SourcesCard', () => {
242
242
  );
243
243
  expect(screen.getByRole('region')).toHaveAttribute('class', 'pf-v6-c-expandable-section__content');
244
244
  });
245
+
246
+ it('should call onClick appropriately', async () => {
247
+ const spy = jest.fn();
248
+ render(<SourcesCard sources={[{ title: 'How to make an apple pie', link: '', onClick: spy }]} />);
249
+ await userEvent.click(screen.getByRole('link', { name: /How to make an apple pie/i }));
250
+ expect(spy).toHaveBeenCalled();
251
+ });
252
+
253
+ it('should apply titleProps appropriately', () => {
254
+ render(
255
+ <SourcesCard sources={[{ title: 'How to make an apple pie', link: '', titleProps: { className: 'test' } }]} />
256
+ );
257
+ expect(screen.getByRole('link', { name: /How to make an apple pie/i })).toHaveClass('test');
258
+ });
245
259
  });
@@ -6,6 +6,7 @@ import { useState } from 'react';
6
6
  // Import PatternFly components
7
7
  import {
8
8
  Button,
9
+ ButtonProps,
9
10
  ButtonVariant,
10
11
  Card,
11
12
  CardBody,
@@ -31,11 +32,20 @@ export interface SourcesCardProps extends CardProps {
31
32
  paginationAriaLabel?: string;
32
33
  /** Content rendered inside the paginated card */
33
34
  sources: {
35
+ /** Title of sources card */
34
36
  title?: string;
37
+ /** Link to source */
35
38
  link: string;
39
+ /** Body of sources card */
36
40
  body?: React.ReactNode | string;
41
+ /** Whether link is external */
37
42
  isExternal?: boolean;
43
+ /** Whether sources card is expandable */
38
44
  hasShowMore?: boolean;
45
+ /** onClick event applied to the title of the Sources card */
46
+ onClick?: React.MouseEventHandler<HTMLButtonElement>;
47
+ /** Any additional props applied to the title of the Sources card */
48
+ titleProps?: ButtonProps;
39
49
  }[];
40
50
  /** Label for the English word "source" */
41
51
  sourceWord?: string;
@@ -107,6 +117,8 @@ const SourcesCard: FunctionComponent<SourcesCardProps> = ({
107
117
  isInline
108
118
  rel={sources[page - 1].isExternal ? 'noreferrer' : undefined}
109
119
  target={sources[page - 1].isExternal ? '_blank' : undefined}
120
+ onClick={sources[page - 1].onClick ?? undefined}
121
+ {...sources[page - 1].titleProps}
110
122
  >
111
123
  {renderTitle(sources[page - 1].title)}
112
124
  </Button>