@@ -3,7 +3,6 @@ import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
33import { AlertContext } from './AlertContext' ;
44import { AlertGroupContext } from './AlertGroupContext' ;
55import 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 /> }
0 commit comments