Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 43 additions & 12 deletions packages/react-core/src/components/Alert/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { capitalize, useOUIAProps, OUIAProps } from '../../helpers';
import { AlertContext } from './AlertContext';
import maxLines from '@patternfly/react-tokens/dist/esm/c_alert__title_max_lines';
import { Tooltip } from '../Tooltip';
import { AlertToggleExpandButton } from './AlertToggleExpandButton';

export enum AlertVariant {
success = 'success',
Expand All @@ -18,43 +19,50 @@ export enum AlertVariant {
}

export interface AlertProps extends Omit<React.HTMLProps<HTMLDivElement>, 'action' | 'title'>, OUIAProps {
/** Adds Alert variant styles */
/** Adds alert variant styles */
variant?: 'success' | 'danger' | 'warning' | 'info' | 'default';
/** Flag to indicate if the Alert is inline */
/** Flag to indicate if the alert is inline */
isInline?: boolean;
/** Title of the Alert */
/** Flag to indicate if the alert is plain */
isPlain?: boolean;
/** Title of the alert */
title: React.ReactNode;
/** Close button; use the AlertActionCloseButton component */
/** Close button; use the alertActionCloseButton component */
actionClose?: React.ReactNode;
/** Action links; use a single AlertActionLink component or multiple wrapped in an array or React.Fragment */
/** Action links; use a single alertActionLink component or multiple wrapped in an array or React.Fragment */
actionLinks?: React.ReactNode;
/** Content rendered inside the Alert */
/** Content rendered inside the alert */
children?: React.ReactNode;
/** Additional classes added to the Alert */
/** Additional classes added to the alert */
className?: string;
/** Adds accessible text to the Alert */
/** Adds accessible text to the alert */
'aria-label'?: string;
/** Variant label text for screen readers */
variantLabel?: string;
/** Flag to indicate if the Alert is in a live region */
/** Flag to indicate if the alert is in a live region */
isLiveRegion?: boolean;
/** If set to true, the timeout is 8000 milliseconds. If a number is provided, alert will be dismissed after that amount of time in milliseconds. */
timeout?: number | boolean;
/** If the user hovers over the Alert and `timeout` expires, this is how long to wait before finally dismissing the Alert */
/** If the user hovers over the alert and `timeout` expires, this is how long to wait before finally dismissing the alert */
timeoutAnimation?: number;
/** Function to be executed on alert timeout. Relevant when the timeout prop is set */
onTimeout?: () => void;
/** Truncate title to number of lines */
truncateTitle?: number;
/** Position of the tooltip which is displayed if text is truncated */
tooltipPosition?: 'auto' | 'top' | 'bottom' | 'left' | 'right';
/** Set a custom icon to the Alert. If not set the icon is set according to the variant */
/** Set a custom icon to the alert. If not set the icon is set according to the variant */
customIcon?: React.ReactNode;
/** Flag indicating that the alert is expandable */
isExpandable?: boolean;
/** Adds accessible text to the alert Toggle */
toggleAriaLabel?: string;
}

export const Alert: React.FunctionComponent<AlertProps> = ({
variant = AlertVariant.default,
isInline = false,
isPlain = false,
isLiveRegion = false,
variantLabel = `${capitalize(variant)} alert:`,
'aria-label': ariaLabel = `${capitalize(variant)} Alert`,
Expand All @@ -71,6 +79,8 @@ export const Alert: React.FunctionComponent<AlertProps> = ({
truncateTitle = 0,
tooltipPosition,
customIcon,
isExpandable = false,
toggleAriaLabel = `${capitalize(variant)} alert details`,
onMouseEnter = () => {},
onMouseLeave = () => {},
...props
Expand Down Expand Up @@ -135,6 +145,11 @@ export const Alert: React.FunctionComponent<AlertProps> = ({
dismissed && onTimeout();
}, [dismissed]);

const [isExpanded, setIsExpanded] = useState(false);
const onToggleExpand = () => {
setIsExpanded(!isExpanded);
};

const myOnMouseEnter = (ev: React.MouseEvent<HTMLDivElement>) => {
setIsMouseOver(true);
setTimedOutAnimation(false);
Expand Down Expand Up @@ -165,6 +180,9 @@ export const Alert: React.FunctionComponent<AlertProps> = ({
className={css(
styles.alert,
isInline && styles.modifiers.inline,
isPlain && styles.modifiers.plain,
isExpandable && styles.modifiers.expandable,
isExpanded && styles.modifiers.expanded,
styles.modifiers[variant as 'success' | 'danger' | 'warning' | 'info'],
className
)}
Expand All @@ -178,6 +196,17 @@ export const Alert: React.FunctionComponent<AlertProps> = ({
onMouseLeave={myOnMouseLeave}
{...props}
>
{isExpandable && (
<AlertContext.Provider value={{ title, variantLabel }}>
<div className={css(styles.alertToggle)}>
<AlertToggleExpandButton
isExpanded={isExpanded}
onToggleExpand={onToggleExpand}
aria-label={toggleAriaLabel}
/>
</div>
</AlertContext.Provider>
)}
<AlertIcon variant={variant} customIcon={customIcon} />
{isTooltipVisible ? (
<Tooltip content={getHeadingContent} position={tooltipPosition}>
Expand All @@ -191,7 +220,9 @@ export const Alert: React.FunctionComponent<AlertProps> = ({
<div className={css(styles.alertAction)}>{actionClose}</div>
</AlertContext.Provider>
)}
{children && <div className={css(styles.alertDescription)}>{children}</div>}
{children && (!isExpandable || (isExpandable && isExpanded)) && (
<div className={css(styles.alertDescription)}>{children}</div>
)}
{actionLinks && <div className={css(styles.alertActionGroup)}>{actionLinks}</div>}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as React from 'react';
import { Button, ButtonProps, ButtonVariant } from '../Button';
import { AlertContext } from './AlertContext';
import AngleRightIcon from '@patternfly/react-icons/dist/esm/icons/angle-right-icon';
import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/Alert/alert';

export interface AlertToggleExpandButtonProps extends ButtonProps {
/** Aria label for the toggle button */
'aria-label'?: string;
/** A callback for when the toggle button is clicked */
onToggleExpand?: () => void;
/** Flag to indicate if the content is expanded */
isExpanded?: boolean;
/** Variant label for the toggle button */
variantLabel?: string;
}

export const AlertToggleExpandButton: React.FunctionComponent<AlertToggleExpandButtonProps> = ({
'aria-label': ariaLabel,
variantLabel,
onToggleExpand,
isExpanded,
...props
}: AlertToggleExpandButtonProps) => {
const { title, variantLabel: alertVariantLabel } = React.useContext(AlertContext);
return (
<Button
variant={ButtonVariant.plain}
onClick={onToggleExpand}
aria-expanded={isExpanded}
aria-label={ariaLabel === '' ? `Toggle ${variantLabel || alertVariantLabel} alert: ${title}` : ariaLabel}
{...props}
>
<span className={css(styles.alertToggleIcon)}>
<AngleRightIcon aria-hidden="true" />
</span>
</Button>
);
};
AlertToggleExpandButton.displayName = 'AlertToggleExpandButton';
19 changes: 19 additions & 0 deletions packages/react-core/src/components/Alert/__tests__/Alert.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@ Object.values(AlertVariant).forEach(variant => {
expect(view).toMatchSnapshot();
});

test('expandable variation', () => {
const view = mount(
<Alert variant={variant} title="Some title" isExpandable>
<p>Success alert description. This should tell the user more information about the alert.</p>
</Alert>
)
expect(view).toMatchSnapshot();
})

test('expandable variation description hidden', () => {
const wrapper = mount(
<Alert variant={variant} title="Some title" isExpandable>
<p>Success alert description. This should tell the user more information about the alert.</p>
</Alert>
)
const descriptionExists = wrapper.find('.pf-c-alert__description').exists();
expect(descriptionExists).toBeFalsy();
})

test('Toast alerts match snapsnot', () => {
const view = mount(
<Alert isLiveRegion={true} variant={variant} aria-label={`${variant} toast alert`} title="Some title">
Expand Down
Loading