Skip to content

Commit 8aa6713

Browse files
authored
Merge pull request #4 from josejulio/RHCLOUD-31230
2 parents a224c87 + add65bd commit 8aa6713

File tree

11 files changed

+388
-17
lines changed

11 files changed

+388
-17
lines changed

packages/module/patternfly-docs/content/extensions/virtual-assistant/examples/VirtualAssistant/VirtualAssistant.md

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,49 @@ sourceLink: https://github.com/patternfly/virtual-assistant/blob/main/packages/m
1515
---
1616

1717
import VirtualAssistant from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistant';
18+
import VirtualAssistantAction from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistantAction';
19+
import { AngleDownIcon } from '@patternfly/react-icons';
1820

19-
The **virtual assistant** description
21+
The **virtual assistant** component renders body of the virtual assistant window.
2022

2123
### Basic example
2224

23-
Example description
25+
A blank example of the virtual assistant body.
2426

2527
```js file="./VirtualAssistantExample.tsx"
2628

2729
```
30+
31+
### Customizing input title and placeholder
32+
33+
You can configure a custom title and placeholder input value using `title` and `inputPlaceholder` props.
34+
35+
36+
```js file="./VirtualAssistantCustomText.tsx"
37+
38+
```
39+
40+
### Listening to messages
41+
42+
The `onSendMessage` property can be used for listening to the send button click.
43+
44+
```js file="./VirtualAssistantMessages.tsx"
45+
46+
```
47+
48+
### Disabling send button
49+
50+
Disabling the send button using `isSendButtonDisabled` prevents it from being clicked.
51+
52+
```js file="./VirtualAssistantDisableOnEmptyText.tsx"
53+
54+
```
55+
56+
### Using custom actions
57+
58+
Custom actions can be added to the assistant body using the `actions` property.
59+
60+
61+
```js file="./VirtualAssistantWithActions.tsx"
62+
63+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react';
2+
import VirtualAssistant from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistant';
3+
4+
export const VirtualAssistantCustomText: React.FunctionComponent = () => (
5+
<VirtualAssistant
6+
title="PatternFly assistant"
7+
inputPlaceholder="You can ask anything in here."
8+
/>
9+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react';
2+
import VirtualAssistant from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistant';
3+
4+
export const VirtualAssistantDisableOnEmptyText: React.FunctionComponent = () => {
5+
6+
const [ message, setMessage ] = React.useState<string>("");
7+
8+
return (
9+
<>
10+
<VirtualAssistant
11+
message={message}
12+
onChangeMessage={(_event, value) => setMessage(value)}
13+
isSendButtonDisabled={message.trim() === ""}
14+
/>
15+
</>
16+
)
17+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react';
2+
import VirtualAssistant from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistant';
3+
4+
export const VirtualAssistantMessages: React.FunctionComponent = () => {
5+
6+
const [ message, setMessage ] = React.useState<string>();
7+
const [ lastMessage, setLastMessage ] = React.useState<string>();
8+
9+
return (
10+
<>
11+
<p><b>Last received message: </b> {lastMessage}</p>
12+
<VirtualAssistant
13+
message={message}
14+
onChangeMessage={(_event, value) => setMessage(value)}
15+
onSendMessage={(message: string) => {
16+
setLastMessage(message);
17+
}}
18+
/>
19+
</>
20+
)
21+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react';
2+
import VirtualAssistant from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistant';
3+
import VirtualAssistantAction from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistantAction';
4+
import { AngleDownIcon } from '@patternfly/react-icons';
5+
6+
export const VirtualAssistantWithActions: React.FunctionComponent = () => (
7+
<VirtualAssistant actions={<>
8+
<VirtualAssistantAction aria-label="Minimize virtual assistant">
9+
<AngleDownIcon/>
10+
</VirtualAssistantAction>
11+
</>} />
12+
);

packages/module/src/VirtualAssistant/VirtualAssistant.test.tsx

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
2-
import { render } from '@testing-library/react';
2+
import { fireEvent, render, screen } from '@testing-library/react';
3+
import '@testing-library/jest-dom'
34
import VirtualAssistant from './VirtualAssistant';
45

56
describe('VirtualAssistant', () => {
@@ -9,4 +10,73 @@ describe('VirtualAssistant', () => {
910
expect(container).toMatchSnapshot();
1011
});
1112

13+
it('should render contents within the assistant', () => {
14+
render(<VirtualAssistant><span>hello world</span></VirtualAssistant>);
15+
expect(screen.getByText("hello world")).toBeTruthy();
16+
});
17+
18+
it('should set custom title', () => {
19+
render(<VirtualAssistant
20+
title="I am a custom title"
21+
/>);
22+
expect(screen.getByText('I am a custom title')).toBeTruthy();
23+
});
24+
25+
it('should set custom input placeholder', () => {
26+
render(<VirtualAssistant
27+
inputPlaceholder="I am a custom placeholder"
28+
/>);
29+
expect(screen.getByPlaceholderText('I am a custom placeholder')).toBeTruthy();
30+
});
31+
32+
it('should set message', () => {
33+
render(<VirtualAssistant
34+
message="I am the message"
35+
/>);
36+
expect(screen.getByText('I am the message')).toBeTruthy();
37+
});
38+
39+
it('should listen to message changes', async () => {
40+
const listener = jest.fn();
41+
render(<VirtualAssistant
42+
onChangeMessage={listener}
43+
/>);
44+
45+
fireEvent.change(screen.getByRole("textbox"), {
46+
target: {
47+
value: 'hello textbox'
48+
}
49+
});
50+
51+
expect(listener).toHaveBeenCalledTimes(1);
52+
expect(listener.mock.lastCall[1]).toBe('hello textbox');
53+
});
54+
55+
it('should listen to send button press', async () => {
56+
const listener = jest.fn();
57+
render(<VirtualAssistant
58+
message={"I am the senate"}
59+
onSendMessage={listener}
60+
/>);
61+
62+
fireEvent.click(screen.getByRole("button"));
63+
64+
expect(listener).toHaveBeenCalledTimes(1);
65+
expect(listener.mock.lastCall[0]).toBe('I am the senate');
66+
});
67+
68+
it('should disable input', () => {
69+
render(<VirtualAssistant
70+
isInputDisabled={true}
71+
/>);
72+
expect(screen.getByRole("textbox")).not.toBeEnabled();
73+
});
74+
75+
it('should disable button', () => {
76+
render(<VirtualAssistant
77+
isSendButtonDisabled={true}
78+
/>);
79+
expect(screen.getByRole("button")).not.toBeEnabled();
80+
});
81+
1282
});
Lines changed: 115 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,122 @@
11
import React from 'react';
2-
import { Text } from '@patternfly/react-core'
2+
import {
3+
Button,
4+
Card,
5+
CardBody,
6+
CardFooter,
7+
CardHeader,
8+
CardTitle,
9+
InputGroup,
10+
InputGroupText,
11+
TextArea
12+
} from '@patternfly/react-core';
13+
import { createUseStyles } from 'react-jss';
14+
import classnames from "clsx";
15+
import { PaperPlaneIcon } from '@patternfly/react-icons';
16+
17+
const useStyles = createUseStyles({
18+
card: {
19+
width: "350px",
20+
height: "550px",
21+
overflow: "hidden",
22+
"@media screen and (max-width: 768px)": {
23+
height: "420px",
24+
width: "100%",
25+
},
26+
},
27+
cardHeader: {
28+
background: "var(--pf-v5-global--BackgroundColor--dark-400)",
29+
},
30+
cardTitle: {
31+
color: "var(--pf-v5-global--Color--light-100)",
32+
},
33+
cardBody: {
34+
backgroundColor: "var(--pf-v5-global--BackgroundColor--100)",
35+
paddingLeft: "var(--pf-v5-global--spacer--md)",
36+
paddingRight: "var(--pf-v5-global--spacer--md)",
37+
paddingTop: "var(--pf-v5-global--spacer--lg)",
38+
overflowY: "scroll",
39+
"&::-webkit-scrollbar": "display: none",
40+
},
41+
cardFooter: {
42+
padding: "0",
43+
},
44+
inputGroup: {
45+
height: "60px",
46+
},
47+
textArea: {
48+
resize: "none",
49+
}
50+
})
351

452
export interface VirtualAssistantProps {
5-
/** Content text */
6-
text?: string;
7-
};
53+
/** Messages rendered within the assistant */
54+
children?: React.ReactNode;
55+
/** Header title for the assistant */
56+
title?: string;
57+
/** Input's placeholder for the assistant */
58+
inputPlaceholder?: string;
59+
/** Input's content */
60+
message?: string;
61+
/** Header actions of the assistant */
62+
actions?: React.ReactNode;
63+
/** Input's content change */
64+
onChangeMessage?: (event: React.ChangeEvent<HTMLTextAreaElement>, value: string) => void;
65+
/** Fire when clicking the Send (Plane) icon */
66+
onSendMessage?: (message: string) => void;
67+
/** Disables the text input */
68+
isInputDisabled?: boolean;
69+
/** Disables the send button */
70+
isSendButtonDisabled?: boolean;
71+
}
872

9-
const VirtualAssistant: React.FunctionComponent<VirtualAssistantProps> = ({ text, ...props }: VirtualAssistantProps) => (
10-
<Text {...props}>{text ?? 'Virtual assistant content'}</Text>
11-
);
73+
export const VirtualAssistant: React.FunctionComponent<VirtualAssistantProps> = ({
74+
children,
75+
title = 'Virtual Assistant',
76+
inputPlaceholder = 'Type a message...',
77+
message = '',
78+
actions,
79+
onChangeMessage,
80+
onSendMessage,
81+
isInputDisabled = false,
82+
isSendButtonDisabled = false,
83+
}: VirtualAssistantProps) => {
84+
const classes = useStyles();
1285

86+
return (
87+
<Card className={classes.card}>
88+
<CardHeader className={classes.cardHeader} actions={actions ? {
89+
actions
90+
} : undefined}>
91+
<CardTitle className={classnames(classes.cardTitle,"pf-v5-u-font-size-xl")}>
92+
{title}
93+
</CardTitle>
94+
</CardHeader>
95+
<CardBody className={classes.cardBody}>
96+
{children}
97+
</CardBody>
98+
<CardFooter className={classes.cardFooter}>
99+
<InputGroup className={classes.inputGroup}>
100+
<TextArea
101+
className={classes.textArea}
102+
placeholder={inputPlaceholder}
103+
value={message}
104+
onChange={onChangeMessage}
105+
type="text"
106+
aria-label="Assistant input"
107+
isDisabled={isInputDisabled}
108+
/>
109+
<InputGroupText>
110+
<Button isDisabled={isSendButtonDisabled} aria-label="Virtual assistant's message" variant="plain" className="pf-v5-u-px-sm" onClick={onSendMessage ? () => {
111+
onSendMessage(message);
112+
} : undefined}>
113+
<PaperPlaneIcon />
114+
</Button>
115+
</InputGroupText>
116+
</InputGroup>
117+
</CardFooter>
118+
</Card>
119+
);
120+
};
13121

14122
export default VirtualAssistant;

0 commit comments

Comments
 (0)