Skip to content

Commit 6b76848

Browse files
author
Eric Olkowski
committed
Updated to use transitionend handler for alert removal
1 parent 1572e7b commit 6b76848

File tree

3 files changed

+66
-21
lines changed

3 files changed

+66
-21
lines changed

packages/react-core/src/components/Alert/Alert.tsx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { Fragment, useEffect, useRef, useState } from 'react';
22
import { css } from '@patternfly/react-styles';
33
import styles from '@patternfly/react-styles/css/components/Alert/alert';
44
import alertGroupStyles from '@patternfly/react-styles/css/components/Alert/alert-group';
5-
import fadeShort from '@patternfly/react-tokens/dist/esm/t_global_motion_duration_fade_short';
65
import { AlertIcon } from './AlertIcon';
76
import { capitalize, useOUIAProps, OUIAProps } from '../../helpers';
87
import { AlertContext } from './AlertContext';
@@ -146,22 +145,48 @@ export const Alert: React.FunctionComponent<AlertProps> = ({
146145
const shouldDismiss = timedOut && timedOutAnimation && !isMouseOver && !containsFocus;
147146
const [isDismissed, setIsDismissed] = React.useState(false);
148147
const { hasAnimations } = React.useContext(AlertGroupContext);
148+
const { offstageRight } = alertGroupStyles.modifiers;
149149

150150
const getParentAlertGroupItem = () => divRef.current?.closest(`.${alertGroupStyles.alertGroupItem}`);
151151
useEffect(() => {
152152
const shouldSetDismissed = shouldDismiss && !isDismissed;
153153
if (shouldSetDismissed && hasAnimations) {
154154
const alertGroupItem = getParentAlertGroupItem();
155-
alertGroupItem?.classList.add(alertGroupStyles.modifiers.offstageRight);
156-
setTimeout(() => {
157-
setIsDismissed(true);
158-
}, parseInt(fadeShort.value));
155+
alertGroupItem?.classList.add(offstageRight);
159156
}
160157

161158
if (shouldSetDismissed && !hasAnimations) {
162159
setIsDismissed(true);
163160
}
164-
}, [shouldDismiss]);
161+
}, [shouldDismiss, hasAnimations, isDismissed]);
162+
163+
React.useEffect(() => {
164+
const prefersReducedMotion = !window.matchMedia('(prefers-reduced-motion: no-preference)')?.matches;
165+
const handleOnTransitionEnd = (event: TransitionEvent) => {
166+
const parentAlertGroupItem = getParentAlertGroupItem();
167+
if (
168+
parentAlertGroupItem?.contains(event.target as Node) &&
169+
// If a user has no motion preference, we want to target the grid template rows transition
170+
// so that the onClose is called after the "slide up" animation of other alerts finishes. Otherwise
171+
// we want to target the opacity transition since no other transition with be firing.
172+
((prefersReducedMotion && event.propertyName === 'opacity') ||
173+
(!prefersReducedMotion && event.propertyName === 'grid-template-rows')) &&
174+
(event.target as HTMLElement).className.includes(offstageRight) &&
175+
!isDismissed &&
176+
shouldDismiss
177+
) {
178+
setIsDismissed(true);
179+
}
180+
};
181+
182+
if (hasAnimations) {
183+
window.addEventListener('transitionend', handleOnTransitionEnd);
184+
}
185+
186+
return () => {
187+
window.removeEventListener('transitionend', handleOnTransitionEnd);
188+
};
189+
}, [hasAnimations, shouldDismiss, isDismissed]);
165190

166191
React.useEffect(() => {
167192
const calculatedTimeout = timeout === true ? 8000 : Number(timeout);

packages/react-core/src/components/Alert/AlertActionCloseButton.tsx

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
33
import { AlertContext } from './AlertContext';
44
import { AlertGroupContext } from './AlertGroupContext';
55
import alertGroupStyles from '@patternfly/react-styles/css/components/Alert/alert-group';
6-
import fadeShort from '@patternfly/react-tokens/dist/esm/t_global_motion_duration_fade_short';
76

87
/** Renders a close button for a dismissable alert when this sub-component is passed into
98
* the alert's actionClose property.
@@ -27,34 +26,55 @@ export const AlertActionCloseButton: React.FunctionComponent<AlertActionCloseBut
2726
variantLabel,
2827
...props
2928
}: AlertActionCloseButtonProps) => {
30-
const [alertIsDismissed, setAlertIsDismissed] = React.useState(false);
29+
const [shouldDismissOnTransition, setShouldDismissOnTransition] = React.useState(false);
3130
const closeButtonRef = React.useRef(null);
3231
const { hasAnimations } = React.useContext(AlertGroupContext);
32+
const { offstageRight } = alertGroupStyles.modifiers;
3333

34-
React.useEffect(() => {
35-
if (alertIsDismissed && hasAnimations) {
36-
closeButtonRef.current
37-
?.closest(`.${alertGroupStyles.alertGroupItem}`)
38-
?.classList.add(alertGroupStyles.modifiers.offstageRight);
39-
}
40-
}, [alertIsDismissed]);
41-
42-
const handleOnClose = () => {
34+
const getParentAlertGroupItem = () => closeButtonRef.current?.closest(`.${alertGroupStyles.alertGroupItem}`);
35+
const handleOnClick = () => {
4336
if (hasAnimations) {
44-
setAlertIsDismissed(true);
45-
setTimeout(() => onClose(), parseInt(fadeShort.value));
37+
getParentAlertGroupItem()?.classList.add(offstageRight);
38+
setShouldDismissOnTransition(true);
4639
} else {
4740
onClose();
4841
}
4942
};
5043

44+
React.useEffect(() => {
45+
const prefersReducedMotion = !window.matchMedia('(prefers-reduced-motion: no-preference)')?.matches;
46+
const handleOnTransitionEnd = (event: TransitionEvent) => {
47+
const parentAlertGroupItem = getParentAlertGroupItem();
48+
if (
49+
shouldDismissOnTransition &&
50+
parentAlertGroupItem?.contains(event.target as Node) &&
51+
// If a user has no motion preference, we want to target the grid template rows transition
52+
// so that the onClose is called after the "slide up" animation of other alerts finishes. Otherwise
53+
// we want to target the opacity transition since no other transition with be firing.
54+
((prefersReducedMotion && event.propertyName === 'opacity') ||
55+
(!prefersReducedMotion && event.propertyName === 'grid-template-rows')) &&
56+
(event.target as HTMLElement).className.includes(offstageRight)
57+
) {
58+
onClose();
59+
}
60+
};
61+
62+
if (hasAnimations) {
63+
window.addEventListener('transitionend', handleOnTransitionEnd);
64+
}
65+
66+
return () => {
67+
window.removeEventListener('transitionend', handleOnTransitionEnd);
68+
};
69+
}, [hasAnimations, shouldDismissOnTransition]);
70+
5171
return (
5272
<AlertContext.Consumer>
5373
{({ title, variantLabel: alertVariantLabel }) => (
5474
<Button
5575
ref={closeButtonRef}
5676
variant={ButtonVariant.plain}
57-
onClick={handleOnClose}
77+
onClick={handleOnClick}
5878
aria-label={ariaLabel === '' ? `Close ${variantLabel || alertVariantLabel} alert: ${title}` : ariaLabel}
5979
className={className}
6080
icon={<TimesIcon />}

packages/react-core/src/components/Alert/AlertGroup.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class AlertGroup extends Component<AlertGroupProps, AlertGroupState> {
6060
const {
6161
className,
6262
children,
63-
hasAnimations,
63+
hasAnimations = true,
6464
isToast,
6565
isLiveRegion,
6666
onOverflowClick,

0 commit comments

Comments
 (0)