diff --git a/cypress/component/ServiceCard.cy.tsx b/cypress/component/ServiceCard.cy.tsx new file mode 100644 index 00000000..300b4eea --- /dev/null +++ b/cypress/component/ServiceCard.cy.tsx @@ -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( + } + helperText='Here is helper text' + ouiaId='Example' + />) + cy.get('[data-ouia-component-id="Example-card"]').should('exist'); + }); + it('renders custom footer', () => { + cy.mount( + } + helperText='Here is helper text' + ouiaId='Example' + footer={<> + + Launch + + + Learn More + > + } + />) + cy.get('[data-ouia-component-id="Example-footer"]').should('exist'); + }) +}); \ No newline at end of file diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/ServiceCard/ServiceCard.md b/packages/module/patternfly-docs/content/extensions/component-groups/examples/ServiceCard/ServiceCard.md new file mode 100644 index 00000000..ddb45061 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/ServiceCard/ServiceCard.md @@ -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" + +``` + diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/ServiceCard/ServiceCardExample.tsx b/packages/module/patternfly-docs/content/extensions/component-groups/examples/ServiceCard/ServiceCardExample.tsx new file mode 100644 index 00000000..ea81007c --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/ServiceCard/ServiceCardExample.tsx @@ -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 = () => ( + } + helperText='Here is helper text' + footer={<> + + Launch + + + Learn More + > + } + /> +); \ No newline at end of file diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/ServiceCard/ServiceCardGalleryExample.tsx b/packages/module/patternfly-docs/content/extensions/component-groups/examples/ServiceCard/ServiceCardGalleryExample.tsx new file mode 100644 index 00000000..c0da0ffc --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/ServiceCard/ServiceCardGalleryExample.tsx @@ -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 = () => ( + + + } + helperText='' + + /> + + + } + helperText='' + footer={<> + + Launch + + + Learn More + > + } + /> + + +) \ No newline at end of file diff --git a/packages/module/src/ServiceCard/ServiceCard.test.tsx b/packages/module/src/ServiceCard/ServiceCard.test.tsx new file mode 100644 index 00000000..8550afae --- /dev/null +++ b/packages/module/src/ServiceCard/ServiceCard.test.tsx @@ -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()).toMatchSnapshot(); + }); +}); \ No newline at end of file diff --git a/packages/module/src/ServiceCard/ServiceCard.tsx b/packages/module/src/ServiceCard/ServiceCard.tsx new file mode 100644 index 00000000..cc5a9095 --- /dev/null +++ b/packages/module/src/ServiceCard/ServiceCard.tsx @@ -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 = ({ + title, + subtitle, + description, + icon, + helperText, + footer = null, + ouiaId='ServiceCard' +}: ServiceCardProps) => { + const classes = useStyles(); + + return ( + + + + {icon} + + + {title} + {subtitle} + + + {description} + + { helperText ? + ( + + {helperText} + + ) : null + } + { footer } + + + ) +} + +export default ServiceCard; \ No newline at end of file diff --git a/packages/module/src/ServiceCard/__snapshots__/ServiceCard.test.tsx.snap b/packages/module/src/ServiceCard/__snapshots__/ServiceCard.test.tsx.snap new file mode 100644 index 00000000..74cff0d1 --- /dev/null +++ b/packages/module/src/ServiceCard/__snapshots__/ServiceCard.test.tsx.snap @@ -0,0 +1,154 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`LogSnippet component should render LogSnippet component 1`] = ` +{ + "asFragment": [Function], + "baseElement": + + + + + + / + + + + Example + + A basic example + + + + + This is a basic ServiceCard Example + + + + + , + "container": + + + + + / + + + + Example + + A basic example + + + + + This is a basic ServiceCard Example + + + + , + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/packages/module/src/ServiceCard/index.ts b/packages/module/src/ServiceCard/index.ts new file mode 100644 index 00000000..57b00957 --- /dev/null +++ b/packages/module/src/ServiceCard/index.ts @@ -0,0 +1,2 @@ +export { default } from './ServiceCard' +export * from './ServiceCard' diff --git a/packages/module/src/index.ts b/packages/module/src/index.ts index eefe1ab4..2f8003bb 100644 --- a/packages/module/src/index.ts +++ b/packages/module/src/index.ts @@ -1,76 +1,79 @@ // this file is autogenerated by generate-index.js, modifying it manually will have no effect -export { default as ActionButton } from './ActionButton'; -export * from './ActionButton'; +export { default as WarningModal } from './WarningModal'; +export * from './WarningModal'; -export { default as ActionMenu } from './ActionMenu'; -export * from './ActionMenu'; +export { default as UnavailableContent } from './UnavailableContent'; +export * from './UnavailableContent'; -export { default as Ansible } from './Ansible'; -export * from './Ansible'; +export { default as TagCount } from './TagCount'; +export * from './TagCount'; -export { default as Battery } from './Battery'; -export * from './Battery'; +export { default as SkeletonTable } from './SkeletonTable'; +export * from './SkeletonTable'; -export { default as BulkSelect } from './BulkSelect'; -export * from './BulkSelect'; +export { default as ShortcutGrid } from './ShortcutGrid'; +export * from './ShortcutGrid'; -export { default as CloseButton } from './CloseButton'; -export * from './CloseButton'; +export { default as Shortcut } from './Shortcut'; +export * from './Shortcut'; -export { default as ColumnManagementModal } from './ColumnManagementModal'; -export * from './ColumnManagementModal'; +export { default as ServiceCard } from './ServiceCard'; +export * from './ServiceCard'; -export { default as ContentHeader } from './ContentHeader'; -export * from './ContentHeader'; +export { default as NotFoundIcon } from './NotFoundIcon'; +export * from './NotFoundIcon'; -export { default as DetailsPage } from './DetailsPage'; -export * from './DetailsPage'; +export { default as NotAuthorized } from './NotAuthorized'; +export * from './NotAuthorized'; -export { default as DetailsPageHeader } from './DetailsPageHeader'; -export * from './DetailsPageHeader'; +export { default as MultiContentCard } from './MultiContentCard'; +export * from './MultiContentCard'; -export { default as ErrorBoundary } from './ErrorBoundary'; -export * from './ErrorBoundary'; +export { default as LogSnippet } from './LogSnippet'; +export * from './LogSnippet'; -export { default as ErrorStack } from './ErrorStack'; -export * from './ErrorStack'; +export { default as InvalidObject } from './InvalidObject'; +export * from './InvalidObject'; + +export { default as HorizontalNav } from './HorizontalNav'; +export * from './HorizontalNav'; export { default as ErrorState } from './ErrorState'; export * from './ErrorState'; -export { default as HorizontalNav } from './HorizontalNav'; -export * from './HorizontalNav'; +export { default as ErrorStack } from './ErrorStack'; +export * from './ErrorStack'; -export { default as InvalidObject } from './InvalidObject'; -export * from './InvalidObject'; +export { default as ErrorBoundary } from './ErrorBoundary'; +export * from './ErrorBoundary'; -export { default as LogSnippet } from './LogSnippet'; -export * from './LogSnippet'; +export { default as DetailsPageHeader } from './DetailsPageHeader'; +export * from './DetailsPageHeader'; -export { default as MultiContentCard } from './MultiContentCard'; -export * from './MultiContentCard'; +export { default as DetailsPage } from './DetailsPage'; +export * from './DetailsPage'; -export { default as NotAuthorized } from './NotAuthorized'; -export * from './NotAuthorized'; +export { default as ContentHeader } from './ContentHeader'; +export * from './ContentHeader'; -export { default as NotFoundIcon } from './NotFoundIcon'; -export * from './NotFoundIcon'; +export { default as ColumnManagementModal } from './ColumnManagementModal'; +export * from './ColumnManagementModal'; -export { default as Shortcut } from './Shortcut'; -export * from './Shortcut'; +export { default as CloseButton } from './CloseButton'; +export * from './CloseButton'; -export { default as ShortcutGrid } from './ShortcutGrid'; -export * from './ShortcutGrid'; +export { default as BulkSelect } from './BulkSelect'; +export * from './BulkSelect'; -export { default as SkeletonTable } from './SkeletonTable'; -export * from './SkeletonTable'; +export { default as Battery } from './Battery'; +export * from './Battery'; -export { default as TagCount } from './TagCount'; -export * from './TagCount'; +export { default as Ansible } from './Ansible'; +export * from './Ansible'; -export { default as UnavailableContent } from './UnavailableContent'; -export * from './UnavailableContent'; +export { default as ActionMenu } from './ActionMenu'; +export * from './ActionMenu'; -export { default as WarningModal } from './WarningModal'; -export * from './WarningModal'; +export { default as ActionButton } from './ActionButton'; +export * from './ActionButton';