diff --git a/packages/react-core/src/components/Button/Button.tsx b/packages/react-core/src/components/Button/Button.tsx index cf389b76551..64b09d6284d 100644 --- a/packages/react-core/src/components/Button/Button.tsx +++ b/packages/react-core/src/components/Button/Button.tsx @@ -4,6 +4,8 @@ import { css } from '@patternfly/react-styles'; import { Spinner, spinnerSize } from '../Spinner'; import { useOUIAProps, OUIAProps } from '../../helpers/OUIA/ouia'; import { Badge } from '../Badge'; +import StarIcon from '@patternfly/react-icons/dist/esm/icons/star-icon'; +import OutlinedStarIcon from '@patternfly/react-icons/dist/esm/icons/outlined-star-icon'; export enum ButtonVariant { primary = 'primary', @@ -71,6 +73,10 @@ export interface ButtonProps extends Omit, 'r inoperableEvents?: string[]; /** Adds inline styling to a link button */ isInline?: boolean; + /** Adds favorite styling to a button */ + isFavorite?: boolean; + /** Flag indicating whether the button is favorited or not, only when isFavorite is true. */ + isFavorited?: boolean; /** Adds styling which affects the size of the button */ size?: 'default' | 'sm' | 'lg'; /** Sets button type */ @@ -117,6 +123,8 @@ const ButtonBase: React.FunctionComponent = ({ size = ButtonSize.default, inoperableEvents = ['onClick', 'onKeyPress'], isInline = false, + isFavorite = false, + isFavorited = false, type = ButtonType.button, variant = ButtonVariant.primary, state = ButtonState.unread, @@ -132,11 +140,19 @@ const ButtonBase: React.FunctionComponent = ({ countOptions, ...props }: ButtonProps) => { + if (isFavorite && !ariaLabel && !props['aria-labelledby']) { + // eslint-disable-next-line no-console + console.error( + 'Button: Each favorite button must have a unique accessible name provided via aria-label or aria-labelledby' + ); + } + const ouiaProps = useOUIAProps(Button.displayName, ouiaId, ouiaSafe, variant); const Component = component as any; const isButtonElement = Component === 'button'; const isInlineSpan = isInline && Component === 'span'; const isIconAlignedAtEnd = iconPosition === 'end' || iconPosition === 'right'; + const shouldOverrideIcon = isFavorite; const preventedEvents = inoperableEvents.reduce( (handlers, eventToPrevent) => ({ @@ -158,11 +174,36 @@ const ButtonBase: React.FunctionComponent = ({ } }; - const _icon = icon && ( - - {icon} - - ); + const renderIcon = () => { + let iconContent; + + if (isFavorite) { + iconContent = ( + <> + + + + + + + + ); + } + + if (icon && !shouldOverrideIcon) { + iconContent = icon; + } + + return ( + iconContent && ( + + {iconContent} + + ) + ); + }; + + const _icon = renderIcon(); const _children = children && {children}; // We only want to render the aria-disabled attribute when true, similar to the disabled attribute natively. const shouldRenderAriaDisabled = isAriaDisabled || (!isButtonElement && isDisabled); @@ -181,6 +222,8 @@ const ButtonBase: React.FunctionComponent = ({ isAriaDisabled && styles.modifiers.ariaDisabled, isClicked && styles.modifiers.clicked, isInline && variant === ButtonVariant.link && styles.modifiers.inline, + isFavorite && styles.modifiers.favorite, + isFavorite && isFavorited && styles.modifiers.favorited, isDanger && (variant === ButtonVariant.secondary || variant === ButtonVariant.link) && styles.modifiers.danger, isLoading !== null && variant !== ButtonVariant.plain && styles.modifiers.progress, isLoading && styles.modifiers.inProgress, diff --git a/packages/react-core/src/components/Button/__tests__/Button.test.tsx b/packages/react-core/src/components/Button/__tests__/Button.test.tsx index bdc5a1c0696..5bcb45233b1 100644 --- a/packages/react-core/src/components/Button/__tests__/Button.test.tsx +++ b/packages/react-core/src/components/Button/__tests__/Button.test.tsx @@ -248,3 +248,28 @@ test(`Renders basic button`, () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); + +test(`Renders with class ${styles.modifiers.favorite} when isFavorite is true`, () => { + render( -``` \ No newline at end of file +``` diff --git a/packages/react-core/src/components/Button/examples/ButtonFavorite.tsx b/packages/react-core/src/components/Button/examples/ButtonFavorite.tsx new file mode 100644 index 00000000000..e888a33e5bb --- /dev/null +++ b/packages/react-core/src/components/Button/examples/ButtonFavorite.tsx @@ -0,0 +1,18 @@ +import { useState } from 'react'; +import { Button } from '@patternfly/react-core'; + +export const ButtonFavorite: React.FunctionComponent = () => { + const [isFavorited, setIsFavorited] = useState(false); + const toggleFavorite = () => { + setIsFavorited(!isFavorited); + }; + return ( +