Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions cypress/component/ServiceCard.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import ServiceCard from '../../packages/module/dist/dynamic/ServiceCard';
import { Button, ButtonVariant } from '@patternfly/react-core';

describe('ServiceCard', () => {
it('renders ServiceCard', () => {
cy.mount(
<ServiceCard
title='Example'
subtitle='A basic example'
description='This is a basic ServiceCard Example'
icon={<img src="/" alt="content-header-icon" />}
helperText='Here is helper text'
ouiaId='Example'
/>)
cy.get('[data-ouia-component-id="Example-card"]').should('exist');
});
it('renders custom footer', () => {
cy.mount(
<ServiceCard
title='Example'
subtitle='A basic example'
description='This is a basic ServiceCard Example'
icon={<img src="/" alt="content-header-icon" />}
helperText='Here is helper text'
ouiaId='Example'
footer={<>
<Button
variant={ButtonVariant.secondary}
isInline
className='pf-v5-u-pr-md'
component="a"
href='www.google.com'>
Launch
</Button>
<Button
variant={ButtonVariant.link}
component="a"
isInline
href='www.google.com'
>
Learn More
</Button></>
}
/>)
cy.get('[data-ouia-component-id="Example-footer"]').should('exist');
})
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
section: extensions
subsection: Component groups
id: Service card
source: react
propComponents: ['ServiceCard']
sourceLink: https://github.com/patternfly/react-component-groups/blob/main/packages/module/patternfly-docs/content/extensions/component-groups/examples/ServiceCard/ServiceCard.md
---

import ServiceCard from "@patternfly/react-component-groups/dist/dynamic/ServiceCard";
import { EllipsisVIcon } from '@patternfly/react-icons';
import contentHeaderIcon from '../../assets/icons/content-header-icon.svg'

The **service card** component displays a card representing a service with an icon, title, description, and an optional customized footer

## Examples

### Service Card

This shows a basic service card with an `icon`, `title`, `description`, and optional footer passed in.

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

```

### Service Card with Gallery example

This shows how cards can look side by side in a [gallery layout](/layouts/gallery).

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

```

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import ServiceCard from "@patternfly/react-component-groups/dist/dynamic/ServiceCard";
import contentHeaderIcon from '../../assets/icons/content-header-icon.svg';
import { Button, ButtonVariant } from '@patternfly/react-core';


export const BasicExample: React.FunctionComponent = () => (
<ServiceCard
title='Example'
subtitle='A basic example'
description='This is a basic ServiceCard Example'
icon={<img src={contentHeaderIcon} alt="content-header-icon" />}
helperText='Here is helper text'
footer={<>
<Button
variant={ButtonVariant.secondary}
isInline
className='pf-v5-u-pr-md'
component="a"
href='www.google.com'>
Launch
</Button>
<Button
variant={ButtonVariant.link}
component="a"
isInline
href='www.google.com'
>
Learn More
</Button></>
}
/>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import ServiceCard from "@patternfly/react-component-groups/dist/dynamic/ServiceCard";
import { Gallery } from '@patternfly/react-core/dist/dynamic/layouts/Gallery';
import { GalleryItem } from '@patternfly/react-core/dist/dynamic/layouts/Gallery';
import { Button, ButtonVariant } from '@patternfly/react-core';
import contentHeaderIcon from '../../assets/icons/content-header-icon.svg';

export const ServiceCardGalleryExample: React.FunctionComponent = () => (
<Gallery hasGutter minWidths={{ default: '330px' }}>
<GalleryItem>
<ServiceCard
title='Example1'
subtitle='A basic example'
description='This is a basic ServiceCard Example'
icon={<img src={contentHeaderIcon} alt="content-header-icon" />}
helperText=''

/>
</GalleryItem>
<GalleryItem>
<ServiceCard
title='Example2'
subtitle='A second example'
description='This is another basic ServiceCard Example'
icon={<img src={contentHeaderIcon} alt="content-header-icon" />}
helperText=''
footer={<>
<Button
variant={ButtonVariant.secondary}
isInline
component="a"
href='www.google.com'>
Launch
</Button>
<Button
variant={ButtonVariant.link}
component="a"
isInline
href='www.google.com'
>
Learn More
</Button></>
}
/>
</GalleryItem>
</Gallery>
)
15 changes: 15 additions & 0 deletions packages/module/src/ServiceCard/ServiceCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { render } from '@testing-library/react';
import ServiceCard from './ServiceCard';

describe('LogSnippet component', () => {
it('should render LogSnippet component', () => {
expect(render(<ServiceCard
title='Example'
subtitle='A basic example'
description='This is a basic ServiceCard Example'
icon='/'
helperText=''
/>)).toMatchSnapshot();
});
});
71 changes: 71 additions & 0 deletions packages/module/src/ServiceCard/ServiceCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import { Card, CardBody, CardFooter, CardHeader, Text, TextContent, TextVariants } from '@patternfly/react-core';
import { HelperText } from '@patternfly/react-core/dist/dynamic/components/HelperText';
import { HelperTextItem } from '@patternfly/react-core/dist/dynamic/components/HelperText';
import { createUseStyles } from 'react-jss';

export interface ServiceCardProps {
/** Service card title */
title: string;
/** Service card subtitle */
subtitle: string;
/** Service card description */
description: string;
/** Service card icon */
icon: React.ReactNode;
/** Optional Service card helper text*/
helperText?: string;
/** Optional footer */
footer?: React.ReactElement | null;
/** Optional custom OUIA ID */
ouiaId?: string | number;
}

const useStyles = createUseStyles({
card: {
height: 'var(--pf-v5-u-h-100)'
},
image: {
marginRight: 'var(--pf-v5-global--spacer--md)',
width: 48
}
});

const ServiceCard: React.FunctionComponent<ServiceCardProps> = ({
title,
subtitle,
description,
icon,
helperText,
footer = null,
ouiaId='ServiceCard'
}: ServiceCardProps) => {
const classes = useStyles();

return (
<Card className={classes.card} ouiaId={`${ouiaId}-card`}>
<CardHeader>
<div className={classes.image}>
{icon}
</div>
<TextContent>
<Text component={TextVariants.h2} ouiaId={`${ouiaId}-title`}>{title}</Text>
{subtitle}
</TextContent>
</CardHeader>
<CardBody data-ouia-component-id={`${ouiaId}-description`}>{description}</CardBody>
<CardFooter data-ouia-component-id={`${ouiaId}-footer`}>
{ helperText ?
( <HelperText data-ouia-component-id={`${ouiaId}-helper-text`}>
<HelperTextItem variant="indeterminate" className="pf-v5-u-mb-lg">
{helperText}
</HelperTextItem>
</HelperText>) : null
}
{ footer }
</CardFooter>
</Card>
)
}

export default ServiceCard;
Loading