package.src.components.Alert.Alert.tsx Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of react-core Show documentation
Show all versions of react-core Show documentation
This library provides a set of common React components for use with the PatternFly reference implementation.
The newest version!
import * as React from 'react';
import { useState } from 'react';
import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/Alert/alert';
import { AlertIcon } from './AlertIcon';
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, TooltipPosition } from '../Tooltip';
import { AlertToggleExpandButton } from './AlertToggleExpandButton';
export enum AlertVariant {
success = 'success',
danger = 'danger',
warning = 'warning',
info = 'info',
custom = 'custom'
}
/** The main alert component. */
export interface AlertProps extends Omit, 'action' | 'title'>, OUIAProps {
/** Close button; use the alert action close button component. */
actionClose?: React.ReactNode;
/** Action links; use a single alert action link component or multiple wrapped in an array
* or React.Fragment.
*/
actionLinks?: React.ReactNode;
/** Content rendered inside the alert. */
children?: React.ReactNode;
/** Additional classes to add to the alert. */
className?: string;
/** Set a custom icon to the alert. If not set the icon is set according to the variant. */
customIcon?: React.ReactNode;
/** Uniquely identifies the alert. */
id?: string;
/** Flag indicating that the alert is expandable. */
isExpandable?: boolean;
/** Flag to indicate if the alert is inline. */
isInline?: boolean;
/** Flag to indicate if the alert is in a live region. */
isLiveRegion?: boolean;
/** Flag to indicate if the alert is plain. */
isPlain?: boolean;
/** Function to be executed on alert timeout. Relevant when the timeout prop is set. */
onTimeout?: () => void;
/** 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.
*/
timeoutAnimation?: number;
/** Title of the alert. */
title: React.ReactNode;
/** Sets the element to use as the alert title. Default is h4. */
component?: keyof JSX.IntrinsicElements;
/** Adds accessible text to the alert toggle. */
toggleAriaLabel?: string;
/** Position of the tooltip which is displayed if text is truncated. */
tooltipPosition?:
| TooltipPosition
| 'auto'
| 'top'
| 'bottom'
| 'left'
| 'right'
| 'top-start'
| 'top-end'
| 'bottom-start'
| 'bottom-end'
| 'left-start'
| 'left-end'
| 'right-start'
| 'right-end';
/** Truncate title to number of lines. */
truncateTitle?: number;
/** Adds alert variant styles. */
variant?: 'success' | 'danger' | 'warning' | 'info' | 'custom';
/** Variant label text for screen readers. */
variantLabel?: string;
/** Value to overwrite the randomly generated data-ouia-component-id.*/
ouiaId?: number | string;
/** Set the value of data-ouia-safe. Only set to true when the component is in a static state, i.e. no animations are occurring. At all other times, this value must be false. */
ouiaSafe?: boolean;
}
export const Alert: React.FunctionComponent = ({
variant = AlertVariant.custom,
isInline = false,
isPlain = false,
isLiveRegion = false,
variantLabel = `${capitalize(variant)} alert:`,
actionClose,
actionLinks,
title,
component = 'h4',
children = '',
className = '',
ouiaId,
ouiaSafe = true,
timeout = false,
timeoutAnimation = 3000,
onTimeout = () => {},
truncateTitle = 0,
tooltipPosition,
customIcon,
isExpandable = false,
toggleAriaLabel = `${capitalize(variant)} alert details`,
onMouseEnter = () => {},
onMouseLeave = () => {},
id,
...props
}: AlertProps) => {
const ouiaProps = useOUIAProps(Alert.displayName, ouiaId, ouiaSafe, variant);
const getHeadingContent = (
{variantLabel}
{title}
);
const titleRef = React.useRef(null);
const TitleComponent = component as any;
const divRef = React.useRef();
const [isTooltipVisible, setIsTooltipVisible] = useState(false);
React.useEffect(() => {
if (!titleRef.current || !truncateTitle) {
return;
}
titleRef.current.style.setProperty(maxLines.name, truncateTitle.toString());
const showTooltip = titleRef.current && titleRef.current.offsetHeight < titleRef.current.scrollHeight;
if (isTooltipVisible !== showTooltip) {
setIsTooltipVisible(showTooltip);
}
}, [titleRef, truncateTitle, isTooltipVisible]);
const [timedOut, setTimedOut] = useState(false);
const [timedOutAnimation, setTimedOutAnimation] = useState(true);
const [isMouseOver, setIsMouseOver] = useState();
const [containsFocus, setContainsFocus] = useState();
const dismissed = timedOut && timedOutAnimation && !isMouseOver && !containsFocus;
React.useEffect(() => {
const calculatedTimeout = timeout === true ? 8000 : Number(timeout);
if (calculatedTimeout > 0) {
const timer = setTimeout(() => setTimedOut(true), calculatedTimeout);
return () => clearTimeout(timer);
}
}, [timeout]);
React.useEffect(() => {
const onDocumentFocus = () => {
if (divRef.current) {
if (divRef.current.contains(document.activeElement)) {
setContainsFocus(true);
setTimedOutAnimation(false);
} else if (containsFocus) {
setContainsFocus(false);
}
}
};
document.addEventListener('focus', onDocumentFocus, true);
return () => document.removeEventListener('focus', onDocumentFocus, true);
}, [containsFocus]);
React.useEffect(() => {
if (containsFocus === false || isMouseOver === false) {
const timer = setTimeout(() => setTimedOutAnimation(true), timeoutAnimation);
return () => clearTimeout(timer);
}
}, [containsFocus, isMouseOver, timeoutAnimation]);
React.useEffect(() => {
dismissed && onTimeout();
}, [dismissed, onTimeout]);
const [isExpanded, setIsExpanded] = useState(false);
const onToggleExpand = () => {
setIsExpanded(!isExpanded);
};
const myOnMouseEnter = (ev: React.MouseEvent) => {
setIsMouseOver(true);
setTimedOutAnimation(false);
onMouseEnter(ev);
};
const myOnMouseLeave = (ev: React.MouseEvent) => {
setIsMouseOver(false);
onMouseLeave(ev);
};
if (dismissed) {
return null;
}
const Title = (
{getHeadingContent}
);
return (
{isExpandable && (
)}
{isTooltipVisible ? (
{Title}
) : (
Title
)}
{actionClose && (
{actionClose}
)}
{children && (!isExpandable || (isExpandable && isExpanded)) && (
{children}
)}
{actionLinks && {actionLinks}}
);
};
Alert.displayName = 'Alert';