From bcb504aefafc287ae883ec310b61ae7ea31803a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pet=C5=99=C3=ADk?= Date: Thu, 4 May 2023 20:33:13 +0200 Subject: [PATCH 01/13] feat(Card) implement new selectable/clickable card design --- .../react-core/src/components/Card/Card.tsx | 21 ++++++++++++--- .../src/components/Card/CardHeader.tsx | 26 +++++++++++++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/packages/react-core/src/components/Card/Card.tsx b/packages/react-core/src/components/Card/Card.tsx index 77a01cc807d..b15ff3cd261 100644 --- a/packages/react-core/src/components/Card/Card.tsx +++ b/packages/react-core/src/components/Card/Card.tsx @@ -20,6 +20,8 @@ export interface CardProps extends React.HTMLProps, OUIAProps { isSelectableRaised?: boolean; /** Modifies the card to include selected styling */ isSelected?: boolean; + /** Modifies the card to include clickable styling */ + isClickable?: boolean; /** Modifies a raised selectable card to have disabled styling */ isDisabledRaised?: boolean; /** Modifies the card to include flat styling */ @@ -50,6 +52,7 @@ interface CardContextProps { cardId: string; registerTitleId: (id: string) => void; isExpanded: boolean; + isClickable: boolean; } interface AriaProps { @@ -60,7 +63,8 @@ interface AriaProps { export const CardContext = React.createContext>({ cardId: '', registerTitleId: () => {}, - isExpanded: false + isExpanded: false, + isClickable: false }); export const Card: React.FunctionComponent = ({ @@ -70,6 +74,7 @@ export const Card: React.FunctionComponent = ({ component = 'div', isCompact = false, isSelectable = false, + isClickable = false, isSelectableRaised = false, isSelected = false, isDisabledRaised = false, @@ -104,9 +109,18 @@ export const Card: React.FunctionComponent = ({ if (isSelectableRaised) { return css(styles.modifiers.selectableRaised, isSelected && styles.modifiers.selectedRaised); } + if (isSelectable && isClickable) { + return css(styles.modifiers.selectable, styles.modifiers.clickable, isSelected && styles.modifiers.selected); + } + if (isSelectable) { return css(styles.modifiers.selectable, isSelected && styles.modifiers.selected); } + + if (isClickable) { + return css(styles.modifiers.clickable, isSelected && styles.modifiers.selected); + } + return ''; }; @@ -136,7 +150,8 @@ export const Card: React.FunctionComponent = ({ value={{ cardId: id, registerTitleId, - isExpanded + isExpanded, + isClickable }} > {hasSelectableInput && ( @@ -146,7 +161,7 @@ export const Card: React.FunctionComponent = ({ {...ariaProps} type="checkbox" checked={isSelected} - onChange={event => onSelectableInputChange(event, id)} + onChange={(event) => onSelectableInputChange(event, id)} disabled={isDisabledRaised} tabIndex={-1} /> diff --git a/packages/react-core/src/components/Card/CardHeader.tsx b/packages/react-core/src/components/Card/CardHeader.tsx index d5a4e5dc0d0..bcd1bd97b7a 100644 --- a/packages/react-core/src/components/Card/CardHeader.tsx +++ b/packages/react-core/src/components/Card/CardHeader.tsx @@ -6,6 +6,7 @@ import { CardHeaderMain } from './CardHeaderMain'; import { CardActions } from './CardActions'; import { Button } from '../Button'; import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon'; +import { Radio } from '../Radio'; export interface CardHeaderActionsObject { /** Actions of the card header */ @@ -14,6 +15,8 @@ export interface CardHeaderActionsObject { hasNoOffset?: boolean; /** Additional classes added to the actions wrapper */ className?: string; + /* Action to call when clickable card is clicked */ + onClickAction?: (event: React.FormEvent) => void; } export interface CardHeaderProps extends React.HTMLProps { @@ -44,7 +47,7 @@ export const CardHeader: React.FunctionComponent = ({ ...props }: CardHeaderProps) => ( - {({ cardId }) => { + {({ cardId, isClickable }) => { const cardHeaderToggle = (
From 301508022285a5a50955f4d4fbd72f937c290445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pet=C5=99=C3=ADk?= Date: Fri, 5 May 2023 14:33:05 +0200 Subject: [PATCH 02/13] selectable and clickable changes --- .../react-core/src/components/Card/Card.tsx | 7 +- .../src/components/Card/CardHeader.tsx | 42 +++++++-- .../components/Card/CardSelectableActions.tsx | 24 ++++++ .../src/components/Card/examples/Card.md | 22 +++-- .../Card/examples/CardClickable.tsx | 86 +++++++++++++++++++ 5 files changed, 164 insertions(+), 17 deletions(-) create mode 100644 packages/react-core/src/components/Card/CardSelectableActions.tsx create mode 100644 packages/react-core/src/components/Card/examples/CardClickable.tsx diff --git a/packages/react-core/src/components/Card/Card.tsx b/packages/react-core/src/components/Card/Card.tsx index b15ff3cd261..d9d6ebcedca 100644 --- a/packages/react-core/src/components/Card/Card.tsx +++ b/packages/react-core/src/components/Card/Card.tsx @@ -53,6 +53,7 @@ interface CardContextProps { registerTitleId: (id: string) => void; isExpanded: boolean; isClickable: boolean; + isSelectable: boolean; } interface AriaProps { @@ -64,7 +65,8 @@ export const CardContext = React.createContext>({ cardId: '', registerTitleId: () => {}, isExpanded: false, - isClickable: false + isClickable: false, + isSelectable: false }); export const Card: React.FunctionComponent = ({ @@ -151,7 +153,8 @@ export const Card: React.FunctionComponent = ({ cardId: id, registerTitleId, isExpanded, - isClickable + isClickable, + isSelectable }} > {hasSelectableInput && ( diff --git a/packages/react-core/src/components/Card/CardHeader.tsx b/packages/react-core/src/components/Card/CardHeader.tsx index bcd1bd97b7a..6bef32e16a0 100644 --- a/packages/react-core/src/components/Card/CardHeader.tsx +++ b/packages/react-core/src/components/Card/CardHeader.tsx @@ -4,6 +4,7 @@ import styles from '@patternfly/react-styles/css/components/Card/card'; import { CardContext } from './Card'; import { CardHeaderMain } from './CardHeaderMain'; import { CardActions } from './CardActions'; +import { CardSelectableActions } from './CardSelectableActions'; import { Button } from '../Button'; import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon'; import { Radio } from '../Radio'; @@ -15,6 +16,15 @@ export interface CardHeaderActionsObject { hasNoOffset?: boolean; /** Additional classes added to the actions wrapper */ className?: string; +} + +export interface CardHeaderSelectableActionsObject { + /** Selectable actions of the card header */ + actions?: React.ReactNode; + /** Flag indicating that the actions have no offset */ + hasNoOffset?: boolean; + /** Additional classes added to the actions wrapper */ + className?: string; /* Action to call when clickable card is clicked */ onClickAction?: (event: React.FormEvent) => void; } @@ -26,6 +36,8 @@ export interface CardHeaderProps extends React.HTMLProps { className?: string; /** Actions of the card header */ actions?: CardHeaderActionsObject; + /** Selectable actions of the card header */ + selectableActions?: CardHeaderSelectableActionsObject; /** ID of the card header. */ id?: string; /** Callback expandable card */ @@ -40,6 +52,7 @@ export const CardHeader: React.FunctionComponent = ({ children, className, actions, + selectableActions, id, onExpand, toggleButtonProps, @@ -47,7 +60,7 @@ export const CardHeader: React.FunctionComponent = ({ ...props }: CardHeaderProps) => ( - {({ cardId, isClickable }) => { + {({ cardId, isClickable, isSelectable }) => { const cardHeaderToggle = (
); + if (actions && !(isClickable && isSelectable)) { + if (isClickable) { + // eslint-disable-next-line no-console + console.warn('Clickable only cards should not use actions'); + } + if (isSelectable) { + // eslint-disable-next-line no-console + console.warn('Selectable only cards should not use actions'); + } + } + return (
= ({ > {onExpand && !isToggleRightAligned && cardHeaderToggle} {isClickable && ( - -
+ + } className={css(styles.radioInput)} hidden - onChange={(event, checked) => checked && actions.onClickAction(event)} + onChange={(event, checked) => checked && selectableActions?.onClickAction?.(event)} id="radio" name="radio" /> -
+ +
+ )} + {isSelectable && ( + + {selectableActions?.actions} )} {actions && ( - {' '} - {actions.actions}{' '} + {actions.actions} )} {children && {children}} diff --git a/packages/react-core/src/components/Card/CardSelectableActions.tsx b/packages/react-core/src/components/Card/CardSelectableActions.tsx new file mode 100644 index 00000000000..122b41b18c5 --- /dev/null +++ b/packages/react-core/src/components/Card/CardSelectableActions.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; +import { css } from '@patternfly/react-styles'; +import styles from '@patternfly/react-styles/css/components/Card/card'; + +export interface CardActionsProps extends React.HTMLProps { + /** Content rendered inside the card action */ + children?: React.ReactNode; + /** Additional classes added to the action */ + className?: string; + /** Flag indicating that the actions have no offset */ + hasNoOffset?: boolean; +} + +export const CardSelectableActions: React.FunctionComponent = ({ + children, + className, + hasNoOffset = false, + ...props +}: CardActionsProps) => ( +
+ {children} +
+); +CardSelectableActions.displayName = 'CardActions'; diff --git a/packages/react-core/src/components/Card/examples/Card.md b/packages/react-core/src/components/Card/examples/Card.md index 449fd16dece..31f7705fb9c 100644 --- a/packages/react-core/src/components/Card/examples/Card.md +++ b/packages/react-core/src/components/Card/examples/Card.md @@ -31,14 +31,14 @@ Most modifiers can be used in combination with each other, except for `isCompact ``` -| Modifier | Description | -| --- | --- | -| isCompact | Modifies the card to include compact styling. Should not be used with isLarge. | -| isFlat | Modifies the card to include flat styling. | -| isRounded | Modifies the card to include rounded border styling. | -| isLarge | Modifies the card to be large. Should not be used with isCompact. | -| isFullHeight | Modifies the card so that it fills the total available height of its container. | -| isPlain | Modifies the card to include plain styling, which removes the border and background. | +| Modifier | Description | +| ------------ | ------------------------------------------------------------------------------------ | +| isCompact | Modifies the card to include compact styling. Should not be used with isLarge. | +| isFlat | Modifies the card to include flat styling. | +| isRounded | Modifies the card to include rounded border styling. | +| isLarge | Modifies the card to be large. Should not be used with isCompact. | +| isFullHeight | Modifies the card so that it fills the total available height of its container. | +| isPlain | Modifies the card to include plain styling, which removes the border and background. | ### Header images and actions @@ -149,3 +149,9 @@ An image can be placed in the card header to show users an icon beside the expan ```ts file='./CardExpandableWithIcon.tsx' ``` + +### Clickable + +```ts file='./CardClickable.tsx' + +``` diff --git a/packages/react-core/src/components/Card/examples/CardClickable.tsx b/packages/react-core/src/components/Card/examples/CardClickable.tsx new file mode 100644 index 00000000000..7b61a876e4c --- /dev/null +++ b/packages/react-core/src/components/Card/examples/CardClickable.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { + Card, + CardHeader, + CardTitle, + CardBody, + Dropdown, + DropdownList, + DropdownItem, + MenuToggle, + MenuToggleElement, + Divider +} from '@patternfly/react-core'; +import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; + +export const CardSelectable: React.FunctionComponent = () => { + const [isKebabOpen, setIsKebabOpen] = React.useState(false); + + const onToggle = (event: React.MouseEvent | undefined) => { + event?.stopPropagation(); + setIsKebabOpen(!isKebabOpen); + }; + + const onSelect = (event: React.MouseEvent | undefined) => { + event?.stopPropagation(); + setIsKebabOpen(false); + }; + + const dropdownItems = ( + <> + Action + {/* Prevent default onClick functionality for example purposes */} + event.preventDefault()}> + Link + + + Disabled Action + + event.preventDefault()}> + Disabled Link + + + Separated Action + event.preventDefault()}> + Separated Link + + + ); + + const headerActions = ( + <> + ) => ( + + + )} + isOpen={isKebabOpen} + onOpenChange={(isOpen: boolean) => setIsKebabOpen(isOpen)} + > + {dropdownItems} + + + ); + + return ( + + + console.log('I got clicked') }} + /> + First card + This is a clickable card. + + + ); +}; From 051342abddc445b7c2e25ee1904f18853f342e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pet=C5=99=C3=ADk?= Date: Fri, 5 May 2023 17:50:46 +0200 Subject: [PATCH 03/13] add selectableVariant, refactor markup --- .../src/components/Card/CardHeader.tsx | 77 +++++++++++++------ .../Card/examples/CardClickable.tsx | 10 ++- 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/packages/react-core/src/components/Card/CardHeader.tsx b/packages/react-core/src/components/Card/CardHeader.tsx index 6bef32e16a0..1ccd311f1e3 100644 --- a/packages/react-core/src/components/Card/CardHeader.tsx +++ b/packages/react-core/src/components/Card/CardHeader.tsx @@ -8,6 +8,7 @@ import { CardSelectableActions } from './CardSelectableActions'; import { Button } from '../Button'; import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon'; import { Radio } from '../Radio'; +import { Checkbox } from '../Checkbox'; export interface CardHeaderActionsObject { /** Actions of the card header */ @@ -20,13 +21,19 @@ export interface CardHeaderActionsObject { export interface CardHeaderSelectableActionsObject { /** Selectable actions of the card header */ - actions?: React.ReactNode; + variant?: 'single' | 'multiple'; /** Flag indicating that the actions have no offset */ hasNoOffset?: boolean; /** Additional classes added to the actions wrapper */ className?: string; + /** ID passed to the action element */ + selectableActionID: string; + /** Aria label passed to the action element */ + selectableActionAriaLabel: string; /* Action to call when clickable card is clicked */ onClickAction?: (event: React.FormEvent) => void; + /* Link to navigate to when clickable card is clicked */ + to?: string; } export interface CardHeaderProps extends React.HTMLProps { @@ -96,30 +103,54 @@ export const CardHeader: React.FunctionComponent = ({ {...props} > {onExpand && !isToggleRightAligned && cardHeaderToggle} - {isClickable && ( - + + {selectableActions && ( - } - className={css(styles.radioInput)} - hidden - onChange={(event, checked) => checked && selectableActions?.onClickAction?.(event)} - id="radio" - name="radio" - /> + {isClickable && ( + <> + - - )} - {isSelectable && ( - - {selectableActions?.actions} - - )} - {actions && ( - - {actions.actions} - - )} + )} + + {actions && ( + + {actions.actions} + + )} + + {children && {children}} {onExpand && isToggleRightAligned && cardHeaderToggle}
diff --git a/packages/react-core/src/components/Card/examples/CardClickable.tsx b/packages/react-core/src/components/Card/examples/CardClickable.tsx index 7b61a876e4c..37374dee980 100644 --- a/packages/react-core/src/components/Card/examples/CardClickable.tsx +++ b/packages/react-core/src/components/Card/examples/CardClickable.tsx @@ -70,13 +70,19 @@ export const CardSelectable: React.FunctionComponent = () => { ); + const id = 'test'; + return ( console.log('I got clicked') }} + selectableActions={{ + // eslint-disable-next-line no-console + onClickAction: () => console.log(id), + selectableActionID: id, + selectableActionAriaLabel: id + }} /> First card This is a clickable card. From 199318e34c36aacb74117dc95d8e91209a5bc2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Pet=C5=99=C3=ADk?= Date: Mon, 8 May 2023 16:33:24 +0200 Subject: [PATCH 04/13] add clickable & selectable --- .../src/components/Card/CardHeader.tsx | 175 ++++++++++-------- .../components/Card/CardSelectableActions.tsx | 3 +- 2 files changed, 100 insertions(+), 78 deletions(-) diff --git a/packages/react-core/src/components/Card/CardHeader.tsx b/packages/react-core/src/components/Card/CardHeader.tsx index 1ccd311f1e3..9cf147fd78d 100644 --- a/packages/react-core/src/components/Card/CardHeader.tsx +++ b/packages/react-core/src/components/Card/CardHeader.tsx @@ -5,6 +5,7 @@ import { CardContext } from './Card'; import { CardHeaderMain } from './CardHeaderMain'; import { CardActions } from './CardActions'; import { CardSelectableActions } from './CardSelectableActions'; +import { CardTitle } from './CardTitle'; import { Button } from '../Button'; import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon'; import { Radio } from '../Radio'; @@ -66,96 +67,118 @@ export const CardHeader: React.FunctionComponent = ({ isToggleRightAligned, ...props }: CardHeaderProps) => ( - - {({ cardId, isClickable, isSelectable }) => { - const cardHeaderToggle = ( -
- -
- ); + + {({ cardId, isClickable, isSelectable }) => { + const cardHeaderToggle = ( +
+ +
+ ); - if (actions && !(isClickable && isSelectable)) { - if (isClickable) { - // eslint-disable-next-line no-console - console.warn('Clickable only cards should not use actions'); - } - if (isSelectable) { - // eslint-disable-next-line no-console - console.warn('Selectable only cards should not use actions'); + if (actions && !(isClickable && isSelectable)) { + if (isClickable) { + // eslint-disable-next-line no-console + console.warn('Clickable only cards should not use actions'); + } + if (isSelectable) { + // eslint-disable-next-line no-console + console.warn('Selectable only cards should not use actions'); + } } - } - return ( -
- {onExpand && !isToggleRightAligned && cardHeaderToggle} - - {selectableActions && ( - - {isClickable && ( - <> -
+ ); + }} +
+ ); CardHeader.displayName = 'CardHeader'; diff --git a/packages/react-core/src/components/Card/CardSelectableActions.tsx b/packages/react-core/src/components/Card/CardSelectableActions.tsx index 122b41b18c5..0a6c32f44a7 100644 --- a/packages/react-core/src/components/Card/CardSelectableActions.tsx +++ b/packages/react-core/src/components/Card/CardSelectableActions.tsx @@ -14,10 +14,9 @@ export interface CardActionsProps extends React.HTMLProps { export const CardSelectableActions: React.FunctionComponent = ({ children, className, - hasNoOffset = false, ...props }: CardActionsProps) => ( -
+
{children}
); From 2a8c96627f48155d4dc017bbed055aeab86ad1ea Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Mon, 8 May 2023 19:32:40 -0400 Subject: [PATCH 05/13] Updated functionality --- .../src/components/Card/CardHeader.tsx | 217 ++++++++---------- .../Card/examples/CardClickable.tsx | 93 ++------ .../react-core/src/components/Radio/Radio.tsx | 9 +- 3 files changed, 127 insertions(+), 192 deletions(-) diff --git a/packages/react-core/src/components/Card/CardHeader.tsx b/packages/react-core/src/components/Card/CardHeader.tsx index 9cf147fd78d..37f28a8c099 100644 --- a/packages/react-core/src/components/Card/CardHeader.tsx +++ b/packages/react-core/src/components/Card/CardHeader.tsx @@ -25,16 +25,22 @@ export interface CardHeaderSelectableActionsObject { variant?: 'single' | 'multiple'; /** Flag indicating that the actions have no offset */ hasNoOffset?: boolean; - /** Additional classes added to the actions wrapper */ + /** Additional classes added to the selectable actions wrapper */ className?: string; - /** ID passed to the action element */ - selectableActionID: string; - /** Aria label passed to the action element */ - selectableActionAriaLabel: string; - /* Action to call when clickable card is clicked */ - onClickAction?: (event: React.FormEvent) => void; - /* Link to navigate to when clickable card is clicked */ + /** ID passed to the selectable or clickable input */ + selectableActionId: string; + /** Adds an accessible label to the selectable or clickable input */ + selectableActionAriaLabel?: string; + /** Adds an accessible label to the selectable or clickable input by passing in a + * space separated list of id's. + */ + selectableActionAriaLabelledby?: string; + /** Action to call when clickable card is clicked */ + onClickAction?: (event: React.FormEvent | React.MouseEvent) => void; + /** Link to navigate to when clickable card is clicked */ to?: string; + /** Name for a group of clickable or selectable cards */ + name?: string; } export interface CardHeaderProps extends React.HTMLProps { @@ -67,118 +73,97 @@ export const CardHeader: React.FunctionComponent = ({ isToggleRightAligned, ...props }: CardHeaderProps) => ( - - {({ cardId, isClickable, isSelectable }) => { - const cardHeaderToggle = ( -
- -
- ); + + {({ cardId, isClickable, isSelectable }) => { + const cardHeaderToggle = ( +
+ +
+ ); - if (actions && !(isClickable && isSelectable)) { - if (isClickable) { - // eslint-disable-next-line no-console - console.warn('Clickable only cards should not use actions'); - } - if (isSelectable) { - // eslint-disable-next-line no-console - console.warn('Selectable only cards should not use actions'); - } + if (actions && !(isClickable && isSelectable)) { + if (isClickable) { + // eslint-disable-next-line no-console + console.warn('Clickable only cards should not use actions'); } + if (isSelectable) { + // eslint-disable-next-line no-console + console.warn('Selectable only cards should not use actions'); + } + } - return ( -
- {onExpand && !isToggleRightAligned && cardHeaderToggle} - - {selectableActions && ( - - {isClickable && isSelectable && ( - // todo add radio - - )} - {isClickable && !isSelectable && ( - <> - } - aria-label={selectableActions.selectableActionAriaLabel} - onChange={(event, checked) => checked && selectableActions?.onClickAction?.(event)} - id={selectableActions.selectableActionID} - name={selectableActions.selectableActionID} - /> - - )} - {isSelectable && - !isClickable && - (selectableActions?.variant && selectableActions.variant === 'single' ? ( - <> - } - className={css(styles.radioInput)} - aria-label={selectableActions.selectableActionAriaLabel} - id={selectableActions.selectableActionID} - name={selectableActions.selectableActionID} - /> - - - ) : ( - <> - - - - ))} - - )} + const handleActionClick = (event: React.FormEvent | React.MouseEvent) => { + if (selectableActions?.onClickAction) { + selectableActions?.onClickAction(event); + } else if (selectableActions?.to) { + window.open(selectableActions?.to, '_blank'); + } + }; - {actions && ( - - {actions.actions} - - )} - + const selectableInputProps = { + className: 'pf-m-standalone', + inputClassName: isClickable && !isSelectable && 'pf-v5-screen-reader', + label: <>, + 'aria-label': selectableActions?.selectableActionAriaLabel, + 'aria-labelledby': selectableActions?.selectableActionAriaLabelledby, + id: selectableActions?.selectableActionId, + name: selectableActions?.name, + onClick: handleActionClick + }; - {isClickable && isSelectable ? ( - - - - - - ) : ( - children && {children} - )} - {onExpand && isToggleRightAligned && cardHeaderToggle} -
+ const selectableInput = + selectableActions?.variant === 'single' || (isClickable && !isSelectable) ? ( + + ) : ( + ); - }} -
- ); + + return ( +
+ {onExpand && !isToggleRightAligned && cardHeaderToggle} + + {actions?.actions} + {selectableActions && (isClickable || isSelectable) && ( + {selectableInput} + )} + + + {isClickable && isSelectable ? ( + + + + + + ) : ( + children && {children} + )} + {onExpand && isToggleRightAligned && cardHeaderToggle} +
+ ); + }} +
+); CardHeader.displayName = 'CardHeader'; diff --git a/packages/react-core/src/components/Card/examples/CardClickable.tsx b/packages/react-core/src/components/Card/examples/CardClickable.tsx index 37374dee980..a5462194e9e 100644 --- a/packages/react-core/src/components/Card/examples/CardClickable.tsx +++ b/packages/react-core/src/components/Card/examples/CardClickable.tsx @@ -1,87 +1,32 @@ import React from 'react'; -import { - Card, - CardHeader, - CardTitle, - CardBody, - Dropdown, - DropdownList, - DropdownItem, - MenuToggle, - MenuToggleElement, - Divider -} from '@patternfly/react-core'; -import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; +import { Card, CardHeader, CardTitle, CardBody } from '@patternfly/react-core'; export const CardSelectable: React.FunctionComponent = () => { - const [isKebabOpen, setIsKebabOpen] = React.useState(false); - - const onToggle = (event: React.MouseEvent | undefined) => { - event?.stopPropagation(); - setIsKebabOpen(!isKebabOpen); - }; - - const onSelect = (event: React.MouseEvent | undefined) => { - event?.stopPropagation(); - setIsKebabOpen(false); - }; - - const dropdownItems = ( - <> - Action - {/* Prevent default onClick functionality for example purposes */} - event.preventDefault()}> - Link - - - Disabled Action - - event.preventDefault()}> - Disabled Link - - - Separated Action - event.preventDefault()}> - Separated Link - - - ); - - const headerActions = ( - <> - ) => ( - - - )} - isOpen={isKebabOpen} - onOpenChange={(isOpen: boolean) => setIsKebabOpen(isOpen)} - > - {dropdownItems} - - - ); - - const id = 'test'; + const id1 = 'clickable-card-1'; + const id2 = 'clickable-card-2'; return ( console.log(id), - selectableActionID: id, - selectableActionAriaLabel: id + onClickAction: () => console.log(id1), + selectableActionId: id1, + selectableActionAriaLabel: id1, + name: 'clickable-cards' + }} + /> + First card + This is a clickable card. + + + First card diff --git a/packages/react-core/src/components/Radio/Radio.tsx b/packages/react-core/src/components/Radio/Radio.tsx index 7d6cf739a78..7a369d6c091 100644 --- a/packages/react-core/src/components/Radio/Radio.tsx +++ b/packages/react-core/src/components/Radio/Radio.tsx @@ -7,8 +7,12 @@ import { getOUIAProps, OUIAProps, getDefaultOUIAId } from '../../helpers'; export interface RadioProps extends Omit, 'disabled' | 'label' | 'onChange' | 'type'>, OUIAProps { - /** Additional classes added to the radio. */ + /** Additional classes added to the radio wrapper. This will be a div element if + * isLabelWrapped is true, otherwise this will be a label element. + */ className?: string; + /** Additional classed added to the radio input */ + inputClassName?: string; /** Id of the radio. */ id: string; /** Flag to show if the radio label is wrapped on small screen. */ @@ -70,6 +74,7 @@ export class Radio extends React.Component 'aria-label': ariaLabel, checked, className, + inputClassName, defaultChecked, isLabelWrapped, isLabelBeforeButton, @@ -93,7 +98,7 @@ export class Radio extends React.Component const inputRendered = ( Date: Tue, 9 May 2023 18:48:00 +0200 Subject: [PATCH 06/13] update examples, add functionality --- .../src/components/Card/CardHeader.tsx | 29 ++-- .../src/components/Card/examples/Card.md | 20 ++- .../Card/examples/CardClickable.tsx | 25 ++- .../Card/examples/CardSelectable.tsx | 149 ++++-------------- .../Card/examples/CardSingleSelectable.tsx | 51 ++++++ 5 files changed, 127 insertions(+), 147 deletions(-) create mode 100644 packages/react-core/src/components/Card/examples/CardSingleSelectable.tsx diff --git a/packages/react-core/src/components/Card/CardHeader.tsx b/packages/react-core/src/components/Card/CardHeader.tsx index 37f28a8c099..4c7eb34f91b 100644 --- a/packages/react-core/src/components/Card/CardHeader.tsx +++ b/packages/react-core/src/components/Card/CardHeader.tsx @@ -41,6 +41,8 @@ export interface CardHeaderSelectableActionsObject { to?: string; /** Name for a group of clickable or selectable cards */ name?: string; + /** Flag indicating that the selectableAction is disabled */ + isDisabled?: boolean; } export interface CardHeaderProps extends React.HTMLProps { @@ -104,10 +106,12 @@ export const CardHeader: React.FunctionComponent = ({ } const handleActionClick = (event: React.FormEvent | React.MouseEvent) => { - if (selectableActions?.onClickAction) { - selectableActions?.onClickAction(event); - } else if (selectableActions?.to) { - window.open(selectableActions?.to, '_blank'); + if (isClickable) { + if (selectableActions?.onClickAction) { + selectableActions?.onClickAction(event); + } else if (selectableActions?.to) { + window.open(selectableActions?.to, '_blank'); + } } }; @@ -119,7 +123,8 @@ export const CardHeader: React.FunctionComponent = ({ 'aria-labelledby': selectableActions?.selectableActionAriaLabelledby, id: selectableActions?.selectableActionId, name: selectableActions?.name, - onClick: handleActionClick + onClick: handleActionClick, + isDisabled: selectableActions?.isDisabled }; const selectableInput = @@ -145,20 +150,10 @@ export const CardHeader: React.FunctionComponent = ({ {selectableInput} )} - - {isClickable && isSelectable ? ( + {children && ( - - - + {children} - ) : ( - children && {children} )} {onExpand && isToggleRightAligned && cardHeaderToggle}
diff --git a/packages/react-core/src/components/Card/examples/Card.md b/packages/react-core/src/components/Card/examples/Card.md index 31f7705fb9c..9d26a051664 100644 --- a/packages/react-core/src/components/Card/examples/Card.md +++ b/packages/react-core/src/components/Card/examples/Card.md @@ -98,14 +98,24 @@ A common use case of this is to set all but one body section to `isFilled={false ``` -### Selectable cards +### Clickable cards + +```ts file='./CardClickable.tsx' + +``` -Selectable cards can only be selected one at a time, and are intended for use with [primary-detail layout](/demos/primary-detail). +### Selectable cards ```ts file='./CardSelectable.tsx' ``` +### Single Selectable cards + +```ts file='./CardSingleSelectable.tsx' + +``` + ### Legacy selectable cards The following example shows a legacy implementation of selectable cards. This example uses the `isSelectable` property instead of `isSelectableRaised`, which is the current recommendation for implementation. `isSelectable` applies selectable styling, but does not apply raised styling on hover and selection as `isSelectableRaised` does. @@ -149,9 +159,3 @@ An image can be placed in the card header to show users an icon beside the expan ```ts file='./CardExpandableWithIcon.tsx' ``` - -### Clickable - -```ts file='./CardClickable.tsx' - -``` diff --git a/packages/react-core/src/components/Card/examples/CardClickable.tsx b/packages/react-core/src/components/Card/examples/CardClickable.tsx index a5462194e9e..61c3c0e7586 100644 --- a/packages/react-core/src/components/Card/examples/CardClickable.tsx +++ b/packages/react-core/src/components/Card/examples/CardClickable.tsx @@ -1,13 +1,14 @@ import React from 'react'; import { Card, CardHeader, CardTitle, CardBody } from '@patternfly/react-core'; -export const CardSelectable: React.FunctionComponent = () => { +export const CardClickable: React.FunctionComponent = () => { const id1 = 'clickable-card-1'; const id2 = 'clickable-card-2'; + const id3 = 'clickable-card-3'; return ( - + { }} /> First card - This is a clickable card. + This card performs an action on click. - + { name: 'clickable-cards' }} /> - First card - This is a clickable card. + Second card + This card navigates to a link on click. + + + + Third card + This card is clickable but disabled. ); diff --git a/packages/react-core/src/components/Card/examples/CardSelectable.tsx b/packages/react-core/src/components/Card/examples/CardSelectable.tsx index 90112a13be8..7ecc3837530 100644 --- a/packages/react-core/src/components/Card/examples/CardSelectable.tsx +++ b/packages/react-core/src/components/Card/examples/CardSelectable.tsx @@ -1,129 +1,46 @@ import React from 'react'; -import { - Card, - CardHeader, - CardTitle, - CardBody, - Dropdown, - DropdownList, - DropdownItem, - MenuToggle, - MenuToggleElement, - Divider -} from '@patternfly/react-core'; -import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon'; +import { Card, CardHeader, CardTitle, CardBody } from '@patternfly/react-core'; -export const CardSelectable: React.FunctionComponent = () => { - const [selected, setSelected] = React.useState(''); - const [isKebabOpen, setIsKebabOpen] = React.useState(false); - - const onKeyDown = (event: React.KeyboardEvent) => { - if (event.target !== event.currentTarget) { - return; - } - if ([' ', 'Enter'].includes(event.key)) { - event.preventDefault(); - const newSelected = event.currentTarget.id === selected ? '' : event.currentTarget.id; - setSelected(newSelected); - } - }; - - const onClick = (event: React.MouseEvent) => { - const newSelected = event.currentTarget.id === selected ? '' : event.currentTarget.id; - setSelected(newSelected); - }; - - const onChange = (_event: React.FormEvent, labelledById: string) => { - const newSelected = labelledById === selected ? '' : labelledById; - setSelected(newSelected); - }; - - const onToggle = (event: React.MouseEvent | undefined) => { - event?.stopPropagation(); - setIsKebabOpen(!isKebabOpen); - }; - - const onSelect = (event: React.MouseEvent | undefined) => { - event?.stopPropagation(); - setIsKebabOpen(false); - }; - - const dropdownItems = ( - <> - Action - {/* Prevent default onClick functionality for example purposes */} - event.preventDefault()}> - Link - - - Disabled Action - - event.preventDefault()}> - Disabled Link - - - Separated Action - event.preventDefault()}> - Separated Link - - - ); - - const headerActions = ( - <> - ) => ( - - - )} - isOpen={isKebabOpen} - onOpenChange={(isOpen: boolean) => setIsKebabOpen(isOpen)} - > - {dropdownItems} - - - ); +export const SelectableCard: React.FunctionComponent = () => { + const id1 = 'selectable-card-1'; + const id2 = 'selectable-card-2'; + const id3 = 'selectable-card-3'; return ( - - + + First card - This is a selectable card. Click me to select me. Click again to deselect me. + This card is selectable. -
- + + Second card - This is a selectable card. Click me to select me. Click again to deselect me. + This card is selectable. -
- + + Third card - This is a raised but disabled card. + This card is selectable but disabled.
); diff --git a/packages/react-core/src/components/Card/examples/CardSingleSelectable.tsx b/packages/react-core/src/components/Card/examples/CardSingleSelectable.tsx new file mode 100644 index 00000000000..014777d205b --- /dev/null +++ b/packages/react-core/src/components/Card/examples/CardSingleSelectable.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { Card, CardHeader, CardTitle, CardBody } from '@patternfly/react-core'; + +export const SingleSelectableCard: React.FunctionComponent = () => { + const id1 = 'single-selectable-card-1'; + const id2 = 'single-selectable-card-2'; + const id3 = 'single-selectable-card-3'; + + return ( + + + + First card + This card is single selectable. + + + + Second card + This card is single selectable. + + + + Third card + This card is single selectable but disabled. + + + ); +}; From 5bc6373e756ac7255d2aba98fa88c01f1fd4b882 Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Wed, 10 May 2023 13:11:36 -0400 Subject: [PATCH 07/13] Update selectableActions api --- .../react-core/src/components/Card/Card.tsx | 25 +++-- .../src/components/Card/CardHeader.tsx | 93 ++++++++++--------- .../components/Card/CardSelectableActions.tsx | 4 +- .../src/components/Card/examples/Card.md | 19 +++- .../Card/examples/CardClickable.tsx | 29 +++--- .../Card/examples/CardLegacySelectable.tsx | 2 + ... => CardLegacySelectableA11yHighlight.tsx} | 2 +- .../Card/examples/CardSelectable.tsx | 51 +++++++--- .../Card/examples/CardSingleSelectable.tsx | 26 +++--- .../src/components/Checkbox/Checkbox.tsx | 7 +- 10 files changed, 157 insertions(+), 101 deletions(-) rename packages/react-core/src/components/Card/examples/{CardSelectableA11yHighlight.tsx => CardLegacySelectableA11yHighlight.tsx} (96%) diff --git a/packages/react-core/src/components/Card/Card.tsx b/packages/react-core/src/components/Card/Card.tsx index d9d6ebcedca..68e4fe3bcc0 100644 --- a/packages/react-core/src/components/Card/Card.tsx +++ b/packages/react-core/src/components/Card/Card.tsx @@ -16,13 +16,15 @@ export interface CardProps extends React.HTMLProps, OUIAProps { isCompact?: boolean; /** Modifies the card to include selectable styling */ isSelectable?: boolean; - /** Specifies the card is selectable, and applies the new raised styling on hover and select */ + /** @deprecated Specifies the card is selectable, and applies raised styling on hover and select */ isSelectableRaised?: boolean; /** Modifies the card to include selected styling */ isSelected?: boolean; /** Modifies the card to include clickable styling */ isClickable?: boolean; - /** Modifies a raised selectable card to have disabled styling */ + /** Modifies a clickable or selectable card to have disabled styling. */ + isDisabled?: boolean; + /** @deprecated Modifies a raised selectable card to have disabled styling */ isDisabledRaised?: boolean; /** Modifies the card to include flat styling */ isFlat?: boolean; @@ -36,11 +38,11 @@ export interface CardProps extends React.HTMLProps, OUIAProps { isPlain?: boolean; /** Flag indicating if a card is expanded. Modifies the card to be expandable. */ isExpanded?: boolean; - /** Flag indicating that the card should render a hidden input to make it selectable */ + /** @deprecated Flag indicating that the card should render a hidden input to make it selectable */ hasSelectableInput?: boolean; - /** Aria label to apply to the selectable input if one is rendered */ + /** @deprecated Aria label to apply to the selectable input if one is rendered */ selectableInputAriaLabel?: string; - /** Callback that executes when the selectable input is changed */ + /** @deprecated Callback that executes when the selectable input is changed */ onSelectableInputChange?: (event: React.FormEvent, labelledBy: string) => void; /** Value to overwrite the randomly generated data-ouia-component-id.*/ ouiaId?: number | string; @@ -54,6 +56,7 @@ interface CardContextProps { isExpanded: boolean; isClickable: boolean; isSelectable: boolean; + isDisabled: boolean; } interface AriaProps { @@ -66,7 +69,8 @@ export const CardContext = React.createContext>({ registerTitleId: () => {}, isExpanded: false, isClickable: false, - isSelectable: false + isSelectable: false, + isDisabled: false }); export const Card: React.FunctionComponent = ({ @@ -77,6 +81,7 @@ export const Card: React.FunctionComponent = ({ isCompact = false, isSelectable = false, isClickable = false, + isDisabled = false, isSelectableRaised = false, isSelected = false, isDisabledRaised = false, @@ -112,7 +117,7 @@ export const Card: React.FunctionComponent = ({ return css(styles.modifiers.selectableRaised, isSelected && styles.modifiers.selectedRaised); } if (isSelectable && isClickable) { - return css(styles.modifiers.selectable, styles.modifiers.clickable, isSelected && styles.modifiers.selected); + return css(styles.modifiers.selectable, styles.modifiers.clickable, isSelected && styles.modifiers.current); } if (isSelectable) { @@ -154,7 +159,8 @@ export const Card: React.FunctionComponent = ({ registerTitleId, isExpanded, isClickable, - isSelectable + isSelectable, + isDisabled }} > {hasSelectableInput && ( @@ -181,9 +187,10 @@ export const Card: React.FunctionComponent = ({ isFullHeight && styles.modifiers.fullHeight, isPlain && styles.modifiers.plain, getSelectableModifiers(), + isDisabled && styles.modifiers.disabled, className )} - tabIndex={isSelectable || isSelectableRaised ? '0' : undefined} + tabIndex={isSelectableRaised ? '0' : undefined} {...props} {...ouiaProps} > diff --git a/packages/react-core/src/components/Card/CardHeader.tsx b/packages/react-core/src/components/Card/CardHeader.tsx index 4c7eb34f91b..8acd7422fd2 100644 --- a/packages/react-core/src/components/Card/CardHeader.tsx +++ b/packages/react-core/src/components/Card/CardHeader.tsx @@ -5,7 +5,6 @@ import { CardContext } from './Card'; import { CardHeaderMain } from './CardHeaderMain'; import { CardActions } from './CardActions'; import { CardSelectableActions } from './CardSelectableActions'; -import { CardTitle } from './CardTitle'; import { Button } from '../Button'; import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon'; import { Radio } from '../Radio'; @@ -21,7 +20,7 @@ export interface CardHeaderActionsObject { } export interface CardHeaderSelectableActionsObject { - /** Selectable actions of the card header */ + /** Determines the type of input to be used for a selectable card. */ variant?: 'single' | 'multiple'; /** Flag indicating that the actions have no offset */ hasNoOffset?: boolean; @@ -35,14 +34,16 @@ export interface CardHeaderSelectableActionsObject { * space separated list of id's. */ selectableActionAriaLabelledby?: string; + /** Callback for when a selectable card input changes */ + onChange?: (event: React.FormEvent, checked: boolean) => void; /** Action to call when clickable card is clicked */ onClickAction?: (event: React.FormEvent | React.MouseEvent) => void; /** Link to navigate to when clickable card is clicked */ to?: string; - /** Name for a group of clickable or selectable cards */ + /** Name for the input element of a clickable or selectable card. */ name?: string; - /** Flag indicating that the selectableAction is disabled */ - isDisabled?: boolean; + /** Flag indicating whether the selectable card input is checked */ + isChecked?: boolean; } export interface CardHeaderProps extends React.HTMLProps { @@ -76,7 +77,7 @@ export const CardHeader: React.FunctionComponent = ({ ...props }: CardHeaderProps) => ( - {({ cardId, isClickable, isSelectable }) => { + {({ cardId, isClickable, isSelectable, isDisabled: isCardDisabled }) => { const cardHeaderToggle = (
); - if (actions && !(isClickable && isSelectable)) { - if (isClickable) { - // eslint-disable-next-line no-console - console.warn('Clickable only cards should not use actions'); - } - if (isSelectable) { - // eslint-disable-next-line no-console - console.warn('Selectable only cards should not use actions'); - } + if (actions?.actions && !(isClickable && isSelectable)) { + // eslint-disable-next-line no-console + console.warn( + `${ + isClickable ? 'Clickable' : 'Selectable' + } only cards should not contain any other actions. If you wish to include additional actions, use a clickable and selectable card.` + ); } const handleActionClick = (event: React.FormEvent | React.MouseEvent) => { if (isClickable) { if (selectableActions?.onClickAction) { - selectableActions?.onClickAction(event); + selectableActions.onClickAction(event); } else if (selectableActions?.to) { - window.open(selectableActions?.to, '_blank'); + window.open(selectableActions.to, '_blank'); } } }; - const selectableInputProps = { - className: 'pf-m-standalone', - inputClassName: isClickable && !isSelectable && 'pf-v5-screen-reader', - label: <>, - 'aria-label': selectableActions?.selectableActionAriaLabel, - 'aria-labelledby': selectableActions?.selectableActionAriaLabelledby, - id: selectableActions?.selectableActionId, - name: selectableActions?.name, - onClick: handleActionClick, - isDisabled: selectableActions?.isDisabled + const getClickableSelectableProps = () => { + const baseProps = { + className: 'pf-m-standalone', + inputClassName: isClickable && !isSelectable && 'pf-v5-screen-reader', + label: <>, + 'aria-label': selectableActions?.selectableActionAriaLabel, + 'aria-labelledby': selectableActions?.selectableActionAriaLabelledby, + id: selectableActions?.selectableActionId, + name: selectableActions?.name, + isDisabled: isCardDisabled + }; + + if (isClickable && !isSelectable) { + return { ...baseProps, onClick: handleActionClick }; + } + if (isSelectable) { + return { ...baseProps, onChange: selectableActions?.onChange, isChecked: selectableActions?.isChecked }; + } + + return baseProps; }; const selectableInput = selectableActions?.variant === 'single' || (isClickable && !isSelectable) ? ( - + ) : ( - + ); return ( @@ -141,20 +150,20 @@ export const CardHeader: React.FunctionComponent = ({ {...props} > {onExpand && !isToggleRightAligned && cardHeaderToggle} - - {actions?.actions} - {selectableActions && (isClickable || isSelectable) && ( - {selectableInput} - )} - - {children && ( - - {children} - + {(actions || selectableActions) && ( + + {actions?.actions} + {selectableActions && (isClickable || isSelectable) && ( + + {selectableInput} + + )} + )} + {children && {children}} {onExpand && isToggleRightAligned && cardHeaderToggle} ); diff --git a/packages/react-core/src/components/Card/CardSelectableActions.tsx b/packages/react-core/src/components/Card/CardSelectableActions.tsx index 0a6c32f44a7..3b86314bdc9 100644 --- a/packages/react-core/src/components/Card/CardSelectableActions.tsx +++ b/packages/react-core/src/components/Card/CardSelectableActions.tsx @@ -7,8 +7,6 @@ export interface CardActionsProps extends React.HTMLProps { children?: React.ReactNode; /** Additional classes added to the action */ className?: string; - /** Flag indicating that the actions have no offset */ - hasNoOffset?: boolean; } export const CardSelectableActions: React.FunctionComponent = ({ @@ -20,4 +18,4 @@ export const CardSelectableActions: React.FunctionComponent = {children} ); -CardSelectableActions.displayName = 'CardActions'; +CardSelectableActions.displayName = 'CardSelectableActions'; diff --git a/packages/react-core/src/components/Card/examples/Card.md b/packages/react-core/src/components/Card/examples/Card.md index 9d26a051664..a1e92b05f45 100644 --- a/packages/react-core/src/components/Card/examples/Card.md +++ b/packages/react-core/src/components/Card/examples/Card.md @@ -3,7 +3,16 @@ id: Card section: components cssPrefix: pf-c-card propComponents: - ['Card', 'CardHeader', 'CardHeaderActionsObject', 'CardTitle', 'CardBody', 'CardFooter', 'CardExpandableContent'] + [ + 'Card', + 'CardHeader', + 'CardHeaderActionsObject', + 'CardHeaderSelectableActionsObject', + 'CardTitle', + 'CardBody', + 'CardFooter', + 'CardExpandableContent' + ] ouia: true --- @@ -120,11 +129,15 @@ A common use case of this is to set all but one body section to `isFilled={false The following example shows a legacy implementation of selectable cards. This example uses the `isSelectable` property instead of `isSelectableRaised`, which is the current recommendation for implementation. `isSelectable` applies selectable styling, but does not apply raised styling on hover and selection as `isSelectableRaised` does. +A `tabIndex={0}` is also manually passed to allow the card to be focused and clicked via keyboard. + ```ts file='./CardLegacySelectable.tsx' ``` -### Selectable card accessibility features +### Legacy selectable card accessibility features + +Note: the following example uses deprecated properties. We recommend using the new `selectableActions` property for the `CardHeader` instead. The following cards demonstrate how the `hasSelectableInput` and `onSelectableInputChange` properties improve accessibility for selectable cards. @@ -138,7 +151,7 @@ The second card does not set `hasSelectableInput` to true, so neither the input We recommend navigating this example using a screen reader to best understand both cards. -```ts file='./CardSelectableA11yHighlight.tsx' +```ts file='./CardLegacySelectableA11yHighlight.tsx' ``` diff --git a/packages/react-core/src/components/Card/examples/CardClickable.tsx b/packages/react-core/src/components/Card/examples/CardClickable.tsx index 61c3c0e7586..ee45a60f49e 100644 --- a/packages/react-core/src/components/Card/examples/CardClickable.tsx +++ b/packages/react-core/src/components/Card/examples/CardClickable.tsx @@ -2,44 +2,45 @@ import React from 'react'; import { Card, CardHeader, CardTitle, CardBody } from '@patternfly/react-core'; export const CardClickable: React.FunctionComponent = () => { - const id1 = 'clickable-card-1'; - const id2 = 'clickable-card-2'; - const id3 = 'clickable-card-3'; + const id1 = 'clickable-card-input-1'; + const id2 = 'clickable-card-input-2'; + const id3 = 'clickable-card-input-3'; return ( - + console.log(id1), + onClickAction: () => console.log(`${id1} clicked`), selectableActionId: id1, - selectableActionAriaLabel: id1, - name: 'clickable-cards' + selectableActionAriaLabelledby: 'clickable-card-example-1', + name: 'clickable-card-example' }} /> First card This card performs an action on click. - + Second card This card navigates to a link on click. - + console.log(`${id3} clicked`), selectableActionId: id3, - selectableActionAriaLabel: id3, - name: 'clickable-cards' + selectableActionAriaLabelledby: 'clickable-card-example-3', + name: 'clickable-card-example' }} /> Third card diff --git a/packages/react-core/src/components/Card/examples/CardLegacySelectable.tsx b/packages/react-core/src/components/Card/examples/CardLegacySelectable.tsx index e3b92c96e8d..a3c9083a3c5 100644 --- a/packages/react-core/src/components/Card/examples/CardLegacySelectable.tsx +++ b/packages/react-core/src/components/Card/examples/CardLegacySelectable.tsx @@ -102,6 +102,7 @@ export const CardLegacySelectable: React.FunctionComponent = () => { isSelectable isSelected={selected === 'legacy-first-card'} hasSelectableInput + tabIndex={0} > First legacy selectable card @@ -116,6 +117,7 @@ export const CardLegacySelectable: React.FunctionComponent = () => { isSelectable isSelected={selected === 'legacy-second-card'} hasSelectableInput + tabIndex={0} > Second legacy selectable card This is a selectable card. Click me to select me. Click again to deselect me. diff --git a/packages/react-core/src/components/Card/examples/CardSelectableA11yHighlight.tsx b/packages/react-core/src/components/Card/examples/CardLegacySelectableA11yHighlight.tsx similarity index 96% rename from packages/react-core/src/components/Card/examples/CardSelectableA11yHighlight.tsx rename to packages/react-core/src/components/Card/examples/CardLegacySelectableA11yHighlight.tsx index d8b9169e249..5ed107335e9 100644 --- a/packages/react-core/src/components/Card/examples/CardSelectableA11yHighlight.tsx +++ b/packages/react-core/src/components/Card/examples/CardLegacySelectableA11yHighlight.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { Card, CardTitle, CardBody } from '@patternfly/react-core'; -export const CardSelectableA11yHighlight: React.FunctionComponent = () => { +export const CardLegacySelectableA11yHighlight: React.FunctionComponent = () => { const [selected, setSelected] = React.useState(''); const onKeyDown = (event: React.KeyboardEvent) => { diff --git a/packages/react-core/src/components/Card/examples/CardSelectable.tsx b/packages/react-core/src/components/Card/examples/CardSelectable.tsx index 7ecc3837530..cbfefbf8f69 100644 --- a/packages/react-core/src/components/Card/examples/CardSelectable.tsx +++ b/packages/react-core/src/components/Card/examples/CardSelectable.tsx @@ -2,41 +2,66 @@ import React from 'react'; import { Card, CardHeader, CardTitle, CardBody } from '@patternfly/react-core'; export const SelectableCard: React.FunctionComponent = () => { - const id1 = 'selectable-card-1'; - const id2 = 'selectable-card-2'; - const id3 = 'selectable-card-3'; + const [isChecked1, setIsChecked1] = React.useState(false); + const [isChecked2, setIsChecked2] = React.useState(false); + const [isChecked3, setIsChecked3] = React.useState(false); + + const id1 = 'selectable-card-input-1'; + const id2 = 'selectable-card-input-2'; + const id3 = 'selectable-card-input-3'; + + const onChange = (event: React.FormEvent, checked: boolean) => { + const name = event.currentTarget.name; + + switch (name) { + case id1: + setIsChecked1(checked); + break; + case id2: + setIsChecked2(checked); + break; + case id3: + setIsChecked3(checked); + break; + } + }; return ( - + First card This card is selectable. - + Second card This card is selectable. - + Third card diff --git a/packages/react-core/src/components/Card/examples/CardSingleSelectable.tsx b/packages/react-core/src/components/Card/examples/CardSingleSelectable.tsx index 014777d205b..902950ff028 100644 --- a/packages/react-core/src/components/Card/examples/CardSingleSelectable.tsx +++ b/packages/react-core/src/components/Card/examples/CardSingleSelectable.tsx @@ -2,44 +2,42 @@ import React from 'react'; import { Card, CardHeader, CardTitle, CardBody } from '@patternfly/react-core'; export const SingleSelectableCard: React.FunctionComponent = () => { - const id1 = 'single-selectable-card-1'; - const id2 = 'single-selectable-card-2'; - const id3 = 'single-selectable-card-3'; + const id1 = 'single-selectable-card-input-1'; + const id2 = 'single-selectable-card-input-2'; + const id3 = 'single-selectable-card-input-3'; return ( - + First card This card is single selectable. - + Second card This card is single selectable. - + diff --git a/packages/react-core/src/components/Checkbox/Checkbox.tsx b/packages/react-core/src/components/Checkbox/Checkbox.tsx index f20ba4e0dba..ce7b22010fa 100644 --- a/packages/react-core/src/components/Checkbox/Checkbox.tsx +++ b/packages/react-core/src/components/Checkbox/Checkbox.tsx @@ -10,6 +10,8 @@ export interface CheckboxProps OUIAProps { /** Additional classes added to the checkbox. */ className?: string; + /** Additional classed added to the radio input */ + inputClassName?: string; /** Flag to show if the checkbox selection is valid or invalid. */ isValid?: boolean; /** Flag to show if the checkbox is disabled. */ @@ -74,6 +76,7 @@ export class Checkbox extends React.Component { const { 'aria-label': ariaLabel, className, + inputClassName, onChange, isValid, isDisabled, @@ -109,14 +112,14 @@ export class Checkbox extends React.Component { elem && (elem.indeterminate = isChecked === null)} + ref={(elem) => elem && (elem.indeterminate = isChecked === null)} {...checkedProps} {...getOUIAProps(Checkbox.displayName, ouiaId !== undefined ? ouiaId : this.state.ouiaStateId, ouiaSafe)} /> From 5e89347681df352419dcc1aec2f62170bb1783ca Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Wed, 10 May 2023 14:00:23 -0400 Subject: [PATCH 08/13] Added clickable and selectable example --- .../src/components/Card/examples/Card.md | 22 ++++- .../Card/examples/CardClickableSelectable.tsx | 91 +++++++++++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 packages/react-core/src/components/Card/examples/CardClickableSelectable.tsx diff --git a/packages/react-core/src/components/Card/examples/Card.md b/packages/react-core/src/components/Card/examples/Card.md index a1e92b05f45..30cff8d97cc 100644 --- a/packages/react-core/src/components/Card/examples/Card.md +++ b/packages/react-core/src/components/Card/examples/Card.md @@ -109,22 +109,42 @@ A common use case of this is to set all but one body section to `isFilled={false ### Clickable cards +A card can perform an action or navigate to an external link by clicking anywhere within the card. + +When a card is meant to be clickable only, you must avoid rendering any other interactive content within the ``. + ```ts file='./CardClickable.tsx' ``` ### Selectable cards +A card can also be selected by clicking anywhere within the card. + +Similar to clickable only cards, you must avoid rendering any other interactive content within the `` when it is meant to be selectable only. Refer to our [clickable and selectable example](#clickable-and-selectable-cards) if you need a card that is both selectable and has other interactive content. + ```ts file='./CardSelectable.tsx' ``` -### Single Selectable cards +### Single selectable cards + +When a group of single selectable cards are related, you should pass the same `name` property to each card's `selectableActions` property. ```ts file='./CardSingleSelectable.tsx' ``` +### Clickable and selectable cards + +A card can be selectable and have additional interactive content by passing both the `isClickable` and `isSelectable` properties to ``. The following example shows how the "clickable" functionality can be rendered anywhere within a selectable card. + +When passing interactive content to a clickable and selectable card that is disabled, you should also ensure the interactive content is disabled as well, if applicable. + +```ts file='./CardClickableSelectable.tsx' + +``` + ### Legacy selectable cards The following example shows a legacy implementation of selectable cards. This example uses the `isSelectable` property instead of `isSelectableRaised`, which is the current recommendation for implementation. `isSelectable` applies selectable styling, but does not apply raised styling on hover and selection as `isSelectableRaised` does. diff --git a/packages/react-core/src/components/Card/examples/CardClickableSelectable.tsx b/packages/react-core/src/components/Card/examples/CardClickableSelectable.tsx new file mode 100644 index 00000000000..2f889e9a1df --- /dev/null +++ b/packages/react-core/src/components/Card/examples/CardClickableSelectable.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import { Card, CardHeader, CardTitle, CardBody, Button } from '@patternfly/react-core'; + +export const CardClickable: React.FunctionComponent = () => { + const [isChecked1, setIsChecked1] = React.useState(false); + const [isChecked2, setIsChecked2] = React.useState(false); + const [isChecked3, setIsChecked3] = React.useState(false); + const [isSelected1, setIsSelected1] = React.useState(false); + + const id1 = 'clickable-selectable-card-input-1'; + const id2 = 'clickable-selectable-card-input-2'; + const id3 = 'clickable-selectable-card-input-3'; + + const onClick = () => { + setIsSelected1((prevState) => !prevState); + }; + + const onChange = (event: React.FormEvent, checked: boolean) => { + const name = event.currentTarget.name; + + switch (name) { + case id1: + setIsChecked1(checked); + break; + case id2: + setIsChecked2(checked); + break; + case id3: + setIsChecked3(checked); + break; + } + }; + + return ( + + + + + + + This card performs an action upon clicking the card title and is selectable. + + + + Second Card + + This card is selectable and has a link in the card body that navigates to + + . + + + + + + + + This card is clickable and selectable, but disabled. + + + ); +}; From 0d84bab453ecd30b264157807a8d164d60ac47c8 Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Wed, 10 May 2023 14:12:10 -0400 Subject: [PATCH 09/13] Updated tests --- .../components/Card/__tests__/Card.test.tsx | 18 +++++++++++------- .../__snapshots__/CardHeader.test.tsx.snap | 4 +--- .../src/components/demos/CardDemo/CardDemo.tsx | 3 ++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/packages/react-core/src/components/Card/__tests__/Card.test.tsx b/packages/react-core/src/components/Card/__tests__/Card.test.tsx index b5a1eaef6d7..2c6e5f94d92 100644 --- a/packages/react-core/src/components/Card/__tests__/Card.test.tsx +++ b/packages/react-core/src/components/Card/__tests__/Card.test.tsx @@ -33,7 +33,7 @@ describe('Card', () => { test('allows passing in a React Component as the component', () => { const Component = () =>
im a div
; - render(); + render(); expect(screen.getByText('im a div')).toBeInTheDocument(); }); @@ -47,16 +47,18 @@ describe('Card', () => { const card = screen.getByText('selectable card'); expect(card).toHaveClass('pf-m-selectable'); - expect(card).toHaveAttribute('tabindex', '0'); }); test('card with isSelectable and isSelected applied ', () => { - render(selected and selectable card); + render( + + selected and selectable card + + ); const card = screen.getByText('selected and selectable card'); expect(card).toHaveClass('pf-m-selectable'); expect(card).toHaveClass('pf-m-selected'); - expect(card).toHaveAttribute('tabindex', '0'); }); test('card with only isSelected applied - not change', () => { @@ -81,7 +83,11 @@ describe('Card', () => { }); test('card with isSelectableRaised and isSelected applied ', () => { - render(raised selected card); + render( + + raised selected card + + ); const card = screen.getByText('raised selected card'); expect(card).toHaveClass('pf-m-selectable-raised'); @@ -154,7 +160,6 @@ describe('Card', () => { }); test('card applies the supplied card title as the aria label of the hidden input', () => { - // this component is used to mock the CardTitle's title registry behavior to keep this a pure unit test const MockCardTitle = ({ children }) => { const { registerTitleId } = React.useContext(CardContext); @@ -179,7 +184,6 @@ describe('Card', () => { }); test('card prioritizes selectableInputAriaLabel over card title labelling via card title', () => { - // this component is used to mock the CardTitle's title registry behavior to keep this a pure unit test const MockCardTitle = ({ children }) => { const { registerTitleId } = React.useContext(CardContext); diff --git a/packages/react-core/src/components/Card/__tests__/__snapshots__/CardHeader.test.tsx.snap b/packages/react-core/src/components/Card/__tests__/__snapshots__/CardHeader.test.tsx.snap index 43ec39e21a4..2e23b19d96b 100644 --- a/packages/react-core/src/components/Card/__tests__/__snapshots__/CardHeader.test.tsx.snap +++ b/packages/react-core/src/components/Card/__tests__/__snapshots__/CardHeader.test.tsx.snap @@ -7,9 +7,7 @@ exports[`CardHeader actions are rendered 1`] = ` >
- -
+ /> `; diff --git a/packages/react-integration/demo-app-ts/src/components/demos/CardDemo/CardDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/CardDemo/CardDemo.tsx index 5dfbd7b9ef2..ef3898d3f24 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/CardDemo/CardDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/CardDemo/CardDemo.tsx @@ -127,7 +127,7 @@ export class CardDemo extends React.Component { Footer


- + Header Body Footer @@ -171,6 +171,7 @@ export class CardDemo extends React.Component { id="selectableCard" isSelectable isSelected={this.state.selected === 'selectableCard'} + tabIndex={0} onKeyDown={this.onKeyDown} > Header From 4903ee5135d06e1f3cd0811bec6fad26fe7adde0 Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Tue, 16 May 2023 08:46:37 -0400 Subject: [PATCH 10/13] Updated legacy verbiage and logic to render selectable cards --- .../src/components/Card/CardHeader.tsx | 25 ++++++++----------- .../src/components/Card/examples/Card.md | 10 ++++---- ...table.tsx => CardDeprecatedSelectable.tsx} | 0 ...CardDeprecatedSelectableA11yHighlight.tsx} | 0 4 files changed, 16 insertions(+), 19 deletions(-) rename packages/react-core/src/components/Card/examples/{CardLegacySelectable.tsx => CardDeprecatedSelectable.tsx} (100%) rename packages/react-core/src/components/Card/examples/{CardLegacySelectableA11yHighlight.tsx => CardDeprecatedSelectableA11yHighlight.tsx} (100%) diff --git a/packages/react-core/src/components/Card/CardHeader.tsx b/packages/react-core/src/components/Card/CardHeader.tsx index 8acd7422fd2..9695e4dda85 100644 --- a/packages/react-core/src/components/Card/CardHeader.tsx +++ b/packages/react-core/src/components/Card/CardHeader.tsx @@ -119,10 +119,10 @@ export const CardHeader: React.FunctionComponent = ({ className: 'pf-m-standalone', inputClassName: isClickable && !isSelectable && 'pf-v5-screen-reader', label: <>, - 'aria-label': selectableActions?.selectableActionAriaLabel, - 'aria-labelledby': selectableActions?.selectableActionAriaLabelledby, - id: selectableActions?.selectableActionId, - name: selectableActions?.name, + 'aria-label': selectableActions.selectableActionAriaLabel, + 'aria-labelledby': selectableActions.selectableActionAriaLabelledby, + id: selectableActions.selectableActionId, + name: selectableActions.name, isDisabled: isCardDisabled }; @@ -130,19 +130,12 @@ export const CardHeader: React.FunctionComponent = ({ return { ...baseProps, onClick: handleActionClick }; } if (isSelectable) { - return { ...baseProps, onChange: selectableActions?.onChange, isChecked: selectableActions?.isChecked }; + return { ...baseProps, onChange: selectableActions.onChange, isChecked: selectableActions.isChecked }; } return baseProps; }; - const selectableInput = - selectableActions?.variant === 'single' || (isClickable && !isSelectable) ? ( - - ) : ( - - ); - return (
= ({ {...props} > {onExpand && !isToggleRightAligned && cardHeaderToggle} - {(actions || selectableActions) && ( + {(actions || (selectableActions && (isClickable || isSelectable))) && ( = ({ {actions?.actions} {selectableActions && (isClickable || isSelectable) && ( - {selectableInput} + {selectableActions?.variant === 'single' || (isClickable && !isSelectable) ? ( + + ) : ( + + )} )} diff --git a/packages/react-core/src/components/Card/examples/Card.md b/packages/react-core/src/components/Card/examples/Card.md index 30cff8d97cc..51873be8834 100644 --- a/packages/react-core/src/components/Card/examples/Card.md +++ b/packages/react-core/src/components/Card/examples/Card.md @@ -145,17 +145,17 @@ When passing interactive content to a clickable and selectable card that is disa ``` -### Legacy selectable cards +### Selectable cards (deprecated) -The following example shows a legacy implementation of selectable cards. This example uses the `isSelectable` property instead of `isSelectableRaised`, which is the current recommendation for implementation. `isSelectable` applies selectable styling, but does not apply raised styling on hover and selection as `isSelectableRaised` does. +The following example shows a deprecated implementation of selectable cards. This example uses the `isSelectable` property instead of `isSelectableRaised`, which is the current recommendation for implementation. `isSelectable` applies selectable styling, but does not apply raised styling on hover and selection as `isSelectableRaised` does. A `tabIndex={0}` is also manually passed to allow the card to be focused and clicked via keyboard. -```ts file='./CardLegacySelectable.tsx' +```ts file='./CardDeprecatedSelectable.tsx' ``` -### Legacy selectable card accessibility features +### Selectable card accessibility features (deprecated) Note: the following example uses deprecated properties. We recommend using the new `selectableActions` property for the `CardHeader` instead. @@ -171,7 +171,7 @@ The second card does not set `hasSelectableInput` to true, so neither the input We recommend navigating this example using a screen reader to best understand both cards. -```ts file='./CardLegacySelectableA11yHighlight.tsx' +```ts file='./CardDeprecatedSelectableA11yHighlight.tsx' ``` diff --git a/packages/react-core/src/components/Card/examples/CardLegacySelectable.tsx b/packages/react-core/src/components/Card/examples/CardDeprecatedSelectable.tsx similarity index 100% rename from packages/react-core/src/components/Card/examples/CardLegacySelectable.tsx rename to packages/react-core/src/components/Card/examples/CardDeprecatedSelectable.tsx diff --git a/packages/react-core/src/components/Card/examples/CardLegacySelectableA11yHighlight.tsx b/packages/react-core/src/components/Card/examples/CardDeprecatedSelectableA11yHighlight.tsx similarity index 100% rename from packages/react-core/src/components/Card/examples/CardLegacySelectableA11yHighlight.tsx rename to packages/react-core/src/components/Card/examples/CardDeprecatedSelectableA11yHighlight.tsx From a44ce917bb52e006075ac382222b22924937f2a7 Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Wed, 17 May 2023 15:52:07 -0400 Subject: [PATCH 11/13] Added prop for link target --- packages/react-core/src/components/Card/CardHeader.tsx | 4 +++- packages/react-core/src/components/Card/examples/Card.md | 2 +- .../react-core/src/components/Card/examples/CardClickable.tsx | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/react-core/src/components/Card/CardHeader.tsx b/packages/react-core/src/components/Card/CardHeader.tsx index 9695e4dda85..862f7fbf9d8 100644 --- a/packages/react-core/src/components/Card/CardHeader.tsx +++ b/packages/react-core/src/components/Card/CardHeader.tsx @@ -40,6 +40,8 @@ export interface CardHeaderSelectableActionsObject { onClickAction?: (event: React.FormEvent | React.MouseEvent) => void; /** Link to navigate to when clickable card is clicked */ to?: string; + /** Flag to indicate whether a clickable card's link should open in a new tab/window. */ + isExternalLink?: boolean; /** Name for the input element of a clickable or selectable card. */ name?: string; /** Flag indicating whether the selectable card input is checked */ @@ -109,7 +111,7 @@ export const CardHeader: React.FunctionComponent = ({ if (selectableActions?.onClickAction) { selectableActions.onClickAction(event); } else if (selectableActions?.to) { - window.open(selectableActions.to, '_blank'); + window.open(selectableActions.to, selectableActions.isExternalLink ? '_blank' : '_self'); } } }; diff --git a/packages/react-core/src/components/Card/examples/Card.md b/packages/react-core/src/components/Card/examples/Card.md index 51873be8834..49db4f6d053 100644 --- a/packages/react-core/src/components/Card/examples/Card.md +++ b/packages/react-core/src/components/Card/examples/Card.md @@ -109,7 +109,7 @@ A common use case of this is to set all but one body section to `isFilled={false ### Clickable cards -A card can perform an action or navigate to an external link by clicking anywhere within the card. +A card can perform an action or navigate to an external link by clicking anywhere within the card. You can also pass in the `isExternalLink` property to `selectableActions` if you want a clickable card's link to open in a new tab or window. When a card is meant to be clickable only, you must avoid rendering any other interactive content within the ``. diff --git a/packages/react-core/src/components/Card/examples/CardClickable.tsx b/packages/react-core/src/components/Card/examples/CardClickable.tsx index ee45a60f49e..a83abaec2df 100644 --- a/packages/react-core/src/components/Card/examples/CardClickable.tsx +++ b/packages/react-core/src/components/Card/examples/CardClickable.tsx @@ -24,14 +24,14 @@ export const CardClickable: React.FunctionComponent = () => { Second card - This card navigates to a link on click. + This card can navigate to a link on click.`. Date: Thu, 18 May 2023 09:45:04 -0400 Subject: [PATCH 12/13] Updated example markup --- .../Card/examples/CardClickable.tsx | 17 +++++---- .../Card/examples/CardClickableSelectable.tsx | 35 ++++++++++--------- .../Card/examples/CardSelectable.tsx | 15 ++++---- .../Card/examples/CardSingleSelectable.tsx | 15 ++++---- 4 files changed, 47 insertions(+), 35 deletions(-) diff --git a/packages/react-core/src/components/Card/examples/CardClickable.tsx b/packages/react-core/src/components/Card/examples/CardClickable.tsx index a83abaec2df..4aa25d8eafc 100644 --- a/packages/react-core/src/components/Card/examples/CardClickable.tsx +++ b/packages/react-core/src/components/Card/examples/CardClickable.tsx @@ -17,8 +17,9 @@ export const CardClickable: React.FunctionComponent = () => { selectableActionAriaLabelledby: 'clickable-card-example-1', name: 'clickable-card-example' }} - /> - First card + > + First card + This card performs an action on click. @@ -29,9 +30,10 @@ export const CardClickable: React.FunctionComponent = () => { selectableActionAriaLabelledby: 'clickable-card-example-2', name: 'clickable-card-example' }} - /> - Second card - This card can navigate to a link on click.`. + > + Second card + + This card can navigate to a link on click. { selectableActionAriaLabelledby: 'clickable-card-example-3', name: 'clickable-card-example' }} - /> - Third card + > + Third card + This card is clickable but disabled. diff --git a/packages/react-core/src/components/Card/examples/CardClickableSelectable.tsx b/packages/react-core/src/components/Card/examples/CardClickableSelectable.tsx index 2f889e9a1df..193257cbbc7 100644 --- a/packages/react-core/src/components/Card/examples/CardClickableSelectable.tsx +++ b/packages/react-core/src/components/Card/examples/CardClickableSelectable.tsx @@ -42,12 +42,13 @@ export const CardClickable: React.FunctionComponent = () => { isChecked: isChecked1, onChange }} - /> - - - + > + + + + This card performs an action upon clicking the card title and is selectable. @@ -59,11 +60,12 @@ export const CardClickable: React.FunctionComponent = () => { isChecked: isChecked2, onChange }} - /> - Second Card + > + Second Card + - This card is selectable and has a link in the card body that navigates to - . @@ -78,12 +80,13 @@ export const CardClickable: React.FunctionComponent = () => { isChecked: isChecked3, onChange }} - /> - - - + > + + + + This card is clickable and selectable, but disabled. diff --git a/packages/react-core/src/components/Card/examples/CardSelectable.tsx b/packages/react-core/src/components/Card/examples/CardSelectable.tsx index cbfefbf8f69..a9de63b669d 100644 --- a/packages/react-core/src/components/Card/examples/CardSelectable.tsx +++ b/packages/react-core/src/components/Card/examples/CardSelectable.tsx @@ -37,8 +37,9 @@ export const SelectableCard: React.FunctionComponent = () => { isChecked: isChecked1, onChange }} - /> - First card + > + First card + This card is selectable. @@ -50,8 +51,9 @@ export const SelectableCard: React.FunctionComponent = () => { isChecked: isChecked2, onChange }} - /> - Second card + > + Second card + This card is selectable. @@ -63,8 +65,9 @@ export const SelectableCard: React.FunctionComponent = () => { isChecked: isChecked3, onChange }} - /> - Third card + > + Third card + This card is selectable but disabled. diff --git a/packages/react-core/src/components/Card/examples/CardSingleSelectable.tsx b/packages/react-core/src/components/Card/examples/CardSingleSelectable.tsx index 902950ff028..6279ac1f6d3 100644 --- a/packages/react-core/src/components/Card/examples/CardSingleSelectable.tsx +++ b/packages/react-core/src/components/Card/examples/CardSingleSelectable.tsx @@ -16,8 +16,9 @@ export const SingleSelectableCard: React.FunctionComponent = () => { name: 'single-selectable-card-example', variant: 'single' }} - /> - First card + > + First card + This card is single selectable. @@ -28,8 +29,9 @@ export const SingleSelectableCard: React.FunctionComponent = () => { name: 'single-selectable-card-example', variant: 'single' }} - /> - Second card + > + Second card + This card is single selectable. @@ -40,8 +42,9 @@ export const SingleSelectableCard: React.FunctionComponent = () => { name: 'single-selectable-card-example', variant: 'single' }} - /> - Third card + > + Third card + This card is single selectable but disabled. From 2ad521e8dc75bb580740554dbc7daba60513e057 Mon Sep 17 00:00:00 2001 From: Eric Olkowski Date: Thu, 18 May 2023 11:18:27 -0400 Subject: [PATCH 13/13] Updated example order --- .../src/components/Card/examples/Card.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/react-core/src/components/Card/examples/Card.md b/packages/react-core/src/components/Card/examples/Card.md index 49db4f6d053..c9f328ba881 100644 --- a/packages/react-core/src/components/Card/examples/Card.md +++ b/packages/react-core/src/components/Card/examples/Card.md @@ -107,35 +107,35 @@ A common use case of this is to set all but one body section to `isFilled={false ``` -### Clickable cards +### Selectable -A card can perform an action or navigate to an external link by clicking anywhere within the card. You can also pass in the `isExternalLink` property to `selectableActions` if you want a clickable card's link to open in a new tab or window. +A card can be selected by clicking anywhere within the card. -When a card is meant to be clickable only, you must avoid rendering any other interactive content within the ``. +You must avoid rendering any other interactive content within the `` when it is meant to be selectable only. Refer to our [clickable and selectable example](#clickable-and-selectable-cards) if you need a card that is both selectable and has other interactive content. -```ts file='./CardClickable.tsx' +```ts file='./CardSelectable.tsx' ``` -### Selectable cards - -A card can also be selected by clicking anywhere within the card. +### Single selectable -Similar to clickable only cards, you must avoid rendering any other interactive content within the `` when it is meant to be selectable only. Refer to our [clickable and selectable example](#clickable-and-selectable-cards) if you need a card that is both selectable and has other interactive content. +When a group of single selectable cards are related, you should pass the same `name` property to each card's `selectableActions` property. -```ts file='./CardSelectable.tsx' +```ts file='./CardSingleSelectable.tsx' ``` -### Single selectable cards +### Clickable -When a group of single selectable cards are related, you should pass the same `name` property to each card's `selectableActions` property. +A card can perform an action or navigate to a link by clicking anywhere within the card. You can also pass in the `isExternalLink` property to `selectableActions` if you want a clickable card's link to open in a new tab or window. -```ts file='./CardSingleSelectable.tsx' +When a card is meant to be clickable only, you must avoid rendering any other interactive content within the ``, similar to selectable cards. + +```ts file='./CardClickable.tsx' ``` -### Clickable and selectable cards +### Clickable and selectable A card can be selectable and have additional interactive content by passing both the `isClickable` and `isSelectable` properties to ``. The following example shows how the "clickable" functionality can be rendered anywhere within a selectable card.