Skip to content

Commit

Permalink
Merge pull request #4 from josejulio/RHCLOUD-31230
Browse files Browse the repository at this point in the history
  • Loading branch information
fhlavac authored Mar 22, 2024
2 parents a224c87 + add65bd commit 8aa6713
Show file tree
Hide file tree
Showing 11 changed files with 388 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,49 @@ sourceLink: https://github.com/patternfly/virtual-assistant/blob/main/packages/m
---

import VirtualAssistant from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistant';
import VirtualAssistantAction from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistantAction';
import { AngleDownIcon } from '@patternfly/react-icons';

The **virtual assistant** description
The **virtual assistant** component renders body of the virtual assistant window.

### Basic example

Example description
A blank example of the virtual assistant body.

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

```

### Customizing input title and placeholder

You can configure a custom title and placeholder input value using `title` and `inputPlaceholder` props.


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

```

### Listening to messages

The `onSendMessage` property can be used for listening to the send button click.

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

```

### Disabling send button

Disabling the send button using `isSendButtonDisabled` prevents it from being clicked.

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

```

### Using custom actions

Custom actions can be added to the assistant body using the `actions` property.


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

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';
import VirtualAssistant from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistant';

export const VirtualAssistantCustomText: React.FunctionComponent = () => (
<VirtualAssistant
title="PatternFly assistant"
inputPlaceholder="You can ask anything in here."
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react';
import VirtualAssistant from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistant';

export const VirtualAssistantDisableOnEmptyText: React.FunctionComponent = () => {

const [ message, setMessage ] = React.useState<string>("");

return (
<>
<VirtualAssistant
message={message}
onChangeMessage={(_event, value) => setMessage(value)}
isSendButtonDisabled={message.trim() === ""}
/>
</>
)
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import VirtualAssistant from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistant';

export const VirtualAssistantMessages: React.FunctionComponent = () => {

const [ message, setMessage ] = React.useState<string>();
const [ lastMessage, setLastMessage ] = React.useState<string>();

return (
<>
<p><b>Last received message: </b> {lastMessage}</p>
<VirtualAssistant
message={message}
onChangeMessage={(_event, value) => setMessage(value)}
onSendMessage={(message: string) => {
setLastMessage(message);
}}
/>
</>
)
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import VirtualAssistant from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistant';
import VirtualAssistantAction from '@patternfly/virtual-assistant/dist/dynamic/VirtualAssistantAction';
import { AngleDownIcon } from '@patternfly/react-icons';

export const VirtualAssistantWithActions: React.FunctionComponent = () => (
<VirtualAssistant actions={<>
<VirtualAssistantAction aria-label="Minimize virtual assistant">
<AngleDownIcon/>
</VirtualAssistantAction>
</>} />
);
72 changes: 71 additions & 1 deletion packages/module/src/VirtualAssistant/VirtualAssistant.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { render } from '@testing-library/react';
import { fireEvent, render, screen } from '@testing-library/react';
import '@testing-library/jest-dom'
import VirtualAssistant from './VirtualAssistant';

describe('VirtualAssistant', () => {
Expand All @@ -9,4 +10,73 @@ describe('VirtualAssistant', () => {
expect(container).toMatchSnapshot();
});

it('should render contents within the assistant', () => {
render(<VirtualAssistant><span>hello world</span></VirtualAssistant>);
expect(screen.getByText("hello world")).toBeTruthy();
});

it('should set custom title', () => {
render(<VirtualAssistant
title="I am a custom title"
/>);
expect(screen.getByText('I am a custom title')).toBeTruthy();
});

it('should set custom input placeholder', () => {
render(<VirtualAssistant
inputPlaceholder="I am a custom placeholder"
/>);
expect(screen.getByPlaceholderText('I am a custom placeholder')).toBeTruthy();
});

it('should set message', () => {
render(<VirtualAssistant
message="I am the message"
/>);
expect(screen.getByText('I am the message')).toBeTruthy();
});

it('should listen to message changes', async () => {
const listener = jest.fn();
render(<VirtualAssistant
onChangeMessage={listener}
/>);

fireEvent.change(screen.getByRole("textbox"), {
target: {
value: 'hello textbox'
}
});

expect(listener).toHaveBeenCalledTimes(1);
expect(listener.mock.lastCall[1]).toBe('hello textbox');
});

it('should listen to send button press', async () => {
const listener = jest.fn();
render(<VirtualAssistant
message={"I am the senate"}
onSendMessage={listener}
/>);

fireEvent.click(screen.getByRole("button"));

expect(listener).toHaveBeenCalledTimes(1);
expect(listener.mock.lastCall[0]).toBe('I am the senate');
});

it('should disable input', () => {
render(<VirtualAssistant
isInputDisabled={true}
/>);
expect(screen.getByRole("textbox")).not.toBeEnabled();
});

it('should disable button', () => {
render(<VirtualAssistant
isSendButtonDisabled={true}
/>);
expect(screen.getByRole("button")).not.toBeEnabled();
});

});
122 changes: 115 additions & 7 deletions packages/module/src/VirtualAssistant/VirtualAssistant.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,122 @@
import React from 'react';
import { Text } from '@patternfly/react-core'
import {
Button,
Card,
CardBody,
CardFooter,
CardHeader,
CardTitle,
InputGroup,
InputGroupText,
TextArea
} from '@patternfly/react-core';
import { createUseStyles } from 'react-jss';
import classnames from "clsx";
import { PaperPlaneIcon } from '@patternfly/react-icons';

const useStyles = createUseStyles({
card: {
width: "350px",
height: "550px",
overflow: "hidden",
"@media screen and (max-width: 768px)": {
height: "420px",
width: "100%",
},
},
cardHeader: {
background: "var(--pf-v5-global--BackgroundColor--dark-400)",
},
cardTitle: {
color: "var(--pf-v5-global--Color--light-100)",
},
cardBody: {
backgroundColor: "var(--pf-v5-global--BackgroundColor--100)",
paddingLeft: "var(--pf-v5-global--spacer--md)",
paddingRight: "var(--pf-v5-global--spacer--md)",
paddingTop: "var(--pf-v5-global--spacer--lg)",
overflowY: "scroll",
"&::-webkit-scrollbar": "display: none",
},
cardFooter: {
padding: "0",
},
inputGroup: {
height: "60px",
},
textArea: {
resize: "none",
}
})

export interface VirtualAssistantProps {
/** Content text */
text?: string;
};
/** Messages rendered within the assistant */
children?: React.ReactNode;
/** Header title for the assistant */
title?: string;
/** Input's placeholder for the assistant */
inputPlaceholder?: string;
/** Input's content */
message?: string;
/** Header actions of the assistant */
actions?: React.ReactNode;
/** Input's content change */
onChangeMessage?: (event: React.ChangeEvent<HTMLTextAreaElement>, value: string) => void;
/** Fire when clicking the Send (Plane) icon */
onSendMessage?: (message: string) => void;
/** Disables the text input */
isInputDisabled?: boolean;
/** Disables the send button */
isSendButtonDisabled?: boolean;
}

const VirtualAssistant: React.FunctionComponent<VirtualAssistantProps> = ({ text, ...props }: VirtualAssistantProps) => (
<Text {...props}>{text ?? 'Virtual assistant content'}</Text>
);
export const VirtualAssistant: React.FunctionComponent<VirtualAssistantProps> = ({
children,
title = 'Virtual Assistant',
inputPlaceholder = 'Type a message...',
message = '',
actions,
onChangeMessage,
onSendMessage,
isInputDisabled = false,
isSendButtonDisabled = false,
}: VirtualAssistantProps) => {
const classes = useStyles();

return (
<Card className={classes.card}>
<CardHeader className={classes.cardHeader} actions={actions ? {
actions
} : undefined}>
<CardTitle className={classnames(classes.cardTitle,"pf-v5-u-font-size-xl")}>
{title}
</CardTitle>
</CardHeader>
<CardBody className={classes.cardBody}>
{children}
</CardBody>
<CardFooter className={classes.cardFooter}>
<InputGroup className={classes.inputGroup}>
<TextArea
className={classes.textArea}
placeholder={inputPlaceholder}
value={message}
onChange={onChangeMessage}
type="text"
aria-label="Assistant input"
isDisabled={isInputDisabled}
/>
<InputGroupText>
<Button isDisabled={isSendButtonDisabled} aria-label="Virtual assistant's message" variant="plain" className="pf-v5-u-px-sm" onClick={onSendMessage ? () => {
onSendMessage(message);
} : undefined}>
<PaperPlaneIcon />
</Button>
</InputGroupText>
</InputGroup>
</CardFooter>
</Card>
);
};

export default VirtualAssistant;
Loading

0 comments on commit 8aa6713

Please sign in to comment.