diff --git a/packages/patternfly-4/react-core/src/components/Card/Card.tsx b/packages/patternfly-4/react-core/src/components/Card/Card.tsx index 82c84702ef8..c87438423c4 100644 --- a/packages/patternfly-4/react-core/src/components/Card/Card.tsx +++ b/packages/patternfly-4/react-core/src/components/Card/Card.tsx @@ -13,6 +13,10 @@ export interface CardProps extends React.HTMLProps { isHoverable?: boolean; /** Modifies the card to include compact styling */ isCompact?: boolean; + /** Modifies the card to include selectable styling */ + isSelectable?: boolean; + /** Modifies the card to include selected styling */ + isSelected?: boolean; } export const Card: React.FunctionComponent = ({ @@ -21,6 +25,8 @@ export const Card: React.FunctionComponent = ({ component = 'article', isHoverable = false, isCompact = false, + isSelectable = false, + isSelected = false, ...props }: CardProps) => { const Component = component as any; @@ -30,8 +36,11 @@ export const Card: React.FunctionComponent = ({ styles.card, isHoverable && styles.modifiers.hoverable, isCompact && styles.modifiers.compact, + isSelectable && styles.modifiers.selectable, + isSelected && isSelectable && styles.modifiers.selected, className )} + tabIndex={isSelectable ? '0' : undefined} {...props} > {children} diff --git a/packages/patternfly-4/react-core/src/components/Card/__tests__/Card.test.tsx b/packages/patternfly-4/react-core/src/components/Card/__tests__/Card.test.tsx index 0a4e610ff31..488182f0525 100644 --- a/packages/patternfly-4/react-core/src/components/Card/__tests__/Card.test.tsx +++ b/packages/patternfly-4/react-core/src/components/Card/__tests__/Card.test.tsx @@ -39,3 +39,22 @@ test('card with isCompact applied ', () => { const view = shallow(); expect(view).toMatchSnapshot(); }); + +test('card with isSelectable applied ', () => { + const view = shallow(); + expect(view.prop('className')).toMatch(/selectable/); + expect(view.prop('tabIndex')).toBe('0'); +}); + +test('card with isSelectable and isSelected applied ', () => { + const view = shallow(); + expect(view.prop('className')).toMatch(/selectable/); + expect(view.prop('className')).toMatch(/selected/); + expect(view.prop('tabIndex')).toBe('0'); +}); + +test('card with only isSelected applied - not change', () => { + const view = shallow(); + expect(view.prop('className')).not.toMatch(/selected/); + expect(view.prop('tabIndex')).toBe(undefined); +}); diff --git a/packages/patternfly-4/react-core/src/components/Card/examples/Card.md b/packages/patternfly-4/react-core/src/components/Card/examples/Card.md index fed6c16bb4b..49bf749e5a9 100644 --- a/packages/patternfly-4/react-core/src/components/Card/examples/Card.md +++ b/packages/patternfly-4/react-core/src/components/Card/examples/Card.md @@ -366,3 +366,91 @@ HoverableCard = () => ( ); ``` + +```js title=Selectable-and-selected +import React from 'react'; +import { Card, CardHead, CardActions, CardHeader, CardBody, Dropdown, DropdownToggle, DropdownItem, DropdownSeparator, DropdownPosition, DropdownDirection, KebabToggle, } from '@patternfly/react-core'; + +class SelectableCard extends React.Component { + constructor(props) { + super(props); + this.state = { + selected: null + }; + this.onKeyDown = event => { + if (event.target !== event.currentTarget) { + return; + } + if ([13, 32].includes(event.keyCode)) { + const newSelected = event.currentTarget.id === this.state.selected ? null : event.currentTarget.id + this.setState({ + selected: newSelected + }) + } + } + this.onClick = event => { + const newSelected = event.currentTarget.id === this.state.selected ? null : event.currentTarget.id + this.setState({ + selected: newSelected + }) + }; + this.onToggle = (isOpen, event) => { + event.stopPropagation() + this.setState({ + isOpen + }); + }; + this.onSelect = event => { + event.stopPropagation() + this.setState({ + isOpen: !this.state.isOpen + }); + }; + } + render() { + const { selected, isOpen} = this.state + const dropdownItems = [ + Link, + + Action + , + + Disabled Link + , + + Disabled Action + , + , + Separated Link, + + Separated Action + + ]; + return ( + <> + + + + } + isOpen={isOpen} + isPlain + dropdownItems={dropdownItems} + position={'right'} + /> + + + First card + This is a selectable card. Click me to select me. Click again to deselect me. + +
+ + Second card + This is a selectable card. Click me to select me. Click again to deselect me. + + + ); + } +} +``` diff --git a/packages/patternfly-4/react-core/src/helpers/constants.ts b/packages/patternfly-4/react-core/src/helpers/constants.ts index 7eb08ddfd7d..4c9020d84e3 100644 --- a/packages/patternfly-4/react-core/src/helpers/constants.ts +++ b/packages/patternfly-4/react-core/src/helpers/constants.ts @@ -1,4 +1,4 @@ -export const KEY_CODES = { ARROW_UP: 38, ARROW_DOWN: 40, ESCAPE_KEY: 27, TAB: 9, ENTER: 13, SPACE: ' ' }; +export const KEY_CODES = { ARROW_UP: 38, ARROW_DOWN: 40, ESCAPE_KEY: 27, TAB: 9, ENTER: 13, SPACE: 32 }; export const SIDE = { RIGHT: 'right', LEFT: 'left', BOTH: 'both', NONE: 'none' }; diff --git a/packages/patternfly-4/react-integration/cypress/integration/card.spec.ts b/packages/patternfly-4/react-integration/cypress/integration/card.spec.ts index 995f80383e8..9d701dec3fc 100644 --- a/packages/patternfly-4/react-integration/cypress/integration/card.spec.ts +++ b/packages/patternfly-4/react-integration/cypress/integration/card.spec.ts @@ -21,7 +21,14 @@ describe('Card Demo Test', () => { it('Verify card is compact', () => { cy.get('article') - .last() + .eq(1) .should('have.class', 'pf-m-compact'); }); + + it('Verify card is selectable and selected', () => { + cy.get('article') + .last() + .should('have.class', 'pf-m-selected') + .should('have.class', 'pf-m-selectable'); + }); }); diff --git a/packages/patternfly-4/react-integration/demo-app-ts/src/components/demos/CardDemo/CardDemo.tsx b/packages/patternfly-4/react-integration/demo-app-ts/src/components/demos/CardDemo/CardDemo.tsx index 4b4cdb6228d..b82e99788a7 100644 --- a/packages/patternfly-4/react-integration/demo-app-ts/src/components/demos/CardDemo/CardDemo.tsx +++ b/packages/patternfly-4/react-integration/demo-app-ts/src/components/demos/CardDemo/CardDemo.tsx @@ -20,6 +20,12 @@ export class CardDemo extends React.Component { Body Footer
+

+ + Header + Body + Footer + ); }