Skip to content

Commit 83dc8fd

Browse files
authored
add ServiceCard component (#200)
1 parent 760b59b commit 83dc8fd

9 files changed

Lines changed: 454 additions & 48 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import ServiceCard from '../../packages/module/dist/dynamic/ServiceCard';
3+
import { Button, ButtonVariant } from '@patternfly/react-core';
4+
5+
describe('ServiceCard', () => {
6+
it('renders ServiceCard', () => {
7+
cy.mount(
8+
<ServiceCard
9+
title='Example'
10+
subtitle='A basic example'
11+
description='This is a basic ServiceCard Example'
12+
icon={<img src="/" alt="content-header-icon" />}
13+
helperText='Here is helper text'
14+
ouiaId='Example'
15+
/>)
16+
cy.get('[data-ouia-component-id="Example-card"]').should('exist');
17+
});
18+
it('renders custom footer', () => {
19+
cy.mount(
20+
<ServiceCard
21+
title='Example'
22+
subtitle='A basic example'
23+
description='This is a basic ServiceCard Example'
24+
icon={<img src="/" alt="content-header-icon" />}
25+
helperText='Here is helper text'
26+
ouiaId='Example'
27+
footer={<>
28+
<Button
29+
variant={ButtonVariant.secondary}
30+
isInline
31+
className='pf-v5-u-pr-md'
32+
component="a"
33+
href='www.google.com'>
34+
Launch
35+
</Button>
36+
<Button
37+
variant={ButtonVariant.link}
38+
component="a"
39+
isInline
40+
href='www.google.com'
41+
>
42+
Learn More
43+
</Button></>
44+
}
45+
/>)
46+
cy.get('[data-ouia-component-id="Example-footer"]').should('exist');
47+
})
48+
});
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
section: extensions
3+
subsection: Component groups
4+
id: Service card
5+
source: react
6+
propComponents: ['ServiceCard']
7+
sourceLink: https://github.com/patternfly/react-component-groups/blob/main/packages/module/patternfly-docs/content/extensions/component-groups/examples/ServiceCard/ServiceCard.md
8+
---
9+
10+
import ServiceCard from "@patternfly/react-component-groups/dist/dynamic/ServiceCard";
11+
import { EllipsisVIcon } from '@patternfly/react-icons';
12+
import contentHeaderIcon from '../../assets/icons/content-header-icon.svg'
13+
14+
The **service card** component displays a card representing a service with an icon, title, description, and an optional customized footer
15+
16+
## Examples
17+
18+
### Service Card
19+
20+
This shows a basic service card with an `icon`, `title`, `description`, and optional footer passed in.
21+
22+
```js file="./ServiceCardExample.tsx"
23+
24+
```
25+
26+
### Service Card with Gallery example
27+
28+
This shows how cards can look side by side in a [gallery layout](/layouts/gallery).
29+
30+
```js file="./ServiceCardGalleryExample.tsx"
31+
32+
```
33+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from 'react';
2+
import ServiceCard from "@patternfly/react-component-groups/dist/dynamic/ServiceCard";
3+
import contentHeaderIcon from '../../assets/icons/content-header-icon.svg';
4+
import { Button, ButtonVariant } from '@patternfly/react-core';
5+
6+
7+
export const BasicExample: React.FunctionComponent = () => (
8+
<ServiceCard
9+
title='Example'
10+
subtitle='A basic example'
11+
description='This is a basic ServiceCard Example'
12+
icon={<img src={contentHeaderIcon} alt="content-header-icon" />}
13+
helperText='Here is helper text'
14+
footer={<>
15+
<Button
16+
variant={ButtonVariant.secondary}
17+
isInline
18+
className='pf-v5-u-pr-md'
19+
component="a"
20+
href='www.google.com'>
21+
Launch
22+
</Button>
23+
<Button
24+
variant={ButtonVariant.link}
25+
component="a"
26+
isInline
27+
href='www.google.com'
28+
>
29+
Learn More
30+
</Button></>
31+
}
32+
/>
33+
);
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import React from 'react';
2+
import ServiceCard from "@patternfly/react-component-groups/dist/dynamic/ServiceCard";
3+
import { Gallery } from '@patternfly/react-core/dist/dynamic/layouts/Gallery';
4+
import { GalleryItem } from '@patternfly/react-core/dist/dynamic/layouts/Gallery';
5+
import { Button, ButtonVariant } from '@patternfly/react-core';
6+
import contentHeaderIcon from '../../assets/icons/content-header-icon.svg';
7+
8+
export const ServiceCardGalleryExample: React.FunctionComponent = () => (
9+
<Gallery hasGutter minWidths={{ default: '330px' }}>
10+
<GalleryItem>
11+
<ServiceCard
12+
title='Example1'
13+
subtitle='A basic example'
14+
description='This is a basic ServiceCard Example'
15+
icon={<img src={contentHeaderIcon} alt="content-header-icon" />}
16+
helperText=''
17+
18+
/>
19+
</GalleryItem>
20+
<GalleryItem>
21+
<ServiceCard
22+
title='Example2'
23+
subtitle='A second example'
24+
description='This is another basic ServiceCard Example'
25+
icon={<img src={contentHeaderIcon} alt="content-header-icon" />}
26+
helperText=''
27+
footer={<>
28+
<Button
29+
variant={ButtonVariant.secondary}
30+
isInline
31+
component="a"
32+
href='www.google.com'>
33+
Launch
34+
</Button>
35+
<Button
36+
variant={ButtonVariant.link}
37+
component="a"
38+
isInline
39+
href='www.google.com'
40+
>
41+
Learn More
42+
</Button></>
43+
}
44+
/>
45+
</GalleryItem>
46+
</Gallery>
47+
)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
import { render } from '@testing-library/react';
3+
import ServiceCard from './ServiceCard';
4+
5+
describe('LogSnippet component', () => {
6+
it('should render LogSnippet component', () => {
7+
expect(render(<ServiceCard
8+
title='Example'
9+
subtitle='A basic example'
10+
description='This is a basic ServiceCard Example'
11+
icon='/'
12+
helperText=''
13+
/>)).toMatchSnapshot();
14+
});
15+
});
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React from 'react';
2+
import { Card, CardBody, CardFooter, CardHeader, Text, TextContent, TextVariants } from '@patternfly/react-core';
3+
import { HelperText } from '@patternfly/react-core/dist/dynamic/components/HelperText';
4+
import { HelperTextItem } from '@patternfly/react-core/dist/dynamic/components/HelperText';
5+
import { createUseStyles } from 'react-jss';
6+
7+
export interface ServiceCardProps {
8+
/** Service card title */
9+
title: string;
10+
/** Service card subtitle */
11+
subtitle: string;
12+
/** Service card description */
13+
description: string;
14+
/** Service card icon */
15+
icon: React.ReactNode;
16+
/** Optional Service card helper text*/
17+
helperText?: string;
18+
/** Optional footer */
19+
footer?: React.ReactElement | null;
20+
/** Optional custom OUIA ID */
21+
ouiaId?: string | number;
22+
}
23+
24+
const useStyles = createUseStyles({
25+
card: {
26+
height: 'var(--pf-v5-u-h-100)'
27+
},
28+
image: {
29+
marginRight: 'var(--pf-v5-global--spacer--md)',
30+
width: 48
31+
}
32+
});
33+
34+
const ServiceCard: React.FunctionComponent<ServiceCardProps> = ({
35+
title,
36+
subtitle,
37+
description,
38+
icon,
39+
helperText,
40+
footer = null,
41+
ouiaId='ServiceCard'
42+
}: ServiceCardProps) => {
43+
const classes = useStyles();
44+
45+
return (
46+
<Card className={classes.card} ouiaId={`${ouiaId}-card`}>
47+
<CardHeader>
48+
<div className={classes.image}>
49+
{icon}
50+
</div>
51+
<TextContent>
52+
<Text component={TextVariants.h2} ouiaId={`${ouiaId}-title`}>{title}</Text>
53+
{subtitle}
54+
</TextContent>
55+
</CardHeader>
56+
<CardBody data-ouia-component-id={`${ouiaId}-description`}>{description}</CardBody>
57+
<CardFooter data-ouia-component-id={`${ouiaId}-footer`}>
58+
{ helperText ?
59+
( <HelperText data-ouia-component-id={`${ouiaId}-helper-text`}>
60+
<HelperTextItem variant="indeterminate" className="pf-v5-u-mb-lg">
61+
{helperText}
62+
</HelperTextItem>
63+
</HelperText>) : null
64+
}
65+
{ footer }
66+
</CardFooter>
67+
</Card>
68+
)
69+
}
70+
71+
export default ServiceCard;

0 commit comments

Comments
 (0)