@patternfly/react-core 6.7.0-prerelease.1 → 6.7.0-prerelease.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/components/package.json +1 -1
  3. package/deprecated/package.json +1 -1
  4. package/dist/dynamic/components/AboutModal/package.json +1 -1
  5. package/dist/dynamic/components/Accordion/package.json +1 -1
  6. package/dist/dynamic/components/ActionList/package.json +1 -1
  7. package/dist/dynamic/components/Alert/package.json +1 -1
  8. package/dist/dynamic/components/Avatar/package.json +1 -1
  9. package/dist/dynamic/components/BackToTop/package.json +1 -1
  10. package/dist/dynamic/components/Backdrop/package.json +1 -1
  11. package/dist/dynamic/components/BackgroundImage/package.json +1 -1
  12. package/dist/dynamic/components/Badge/package.json +1 -1
  13. package/dist/dynamic/components/Banner/package.json +1 -1
  14. package/dist/dynamic/components/Brand/package.json +1 -1
  15. package/dist/dynamic/components/Breadcrumb/package.json +1 -1
  16. package/dist/dynamic/components/Button/package.json +1 -1
  17. package/dist/dynamic/components/CalendarMonth/package.json +1 -1
  18. package/dist/dynamic/components/Card/package.json +1 -1
  19. package/dist/dynamic/components/Checkbox/package.json +1 -1
  20. package/dist/dynamic/components/ClipboardCopy/package.json +1 -1
  21. package/dist/dynamic/components/CodeBlock/package.json +1 -1
  22. package/dist/dynamic/components/Compass/package.json +1 -1
  23. package/dist/dynamic/components/Content/package.json +1 -1
  24. package/dist/dynamic/components/DataList/package.json +1 -1
  25. package/dist/dynamic/components/DatePicker/package.json +1 -1
  26. package/dist/dynamic/components/DescriptionList/package.json +1 -1
  27. package/dist/dynamic/components/Divider/package.json +1 -1
  28. package/dist/dynamic/components/Drawer/package.json +1 -1
  29. package/dist/dynamic/components/Dropdown/package.json +1 -1
  30. package/dist/dynamic/components/DualListSelector/package.json +1 -1
  31. package/dist/dynamic/components/EmptyState/package.json +1 -1
  32. package/dist/dynamic/components/ExpandableSection/package.json +1 -1
  33. package/dist/dynamic/components/FileUpload/package.json +1 -1
  34. package/dist/dynamic/components/Form/package.json +1 -1
  35. package/dist/dynamic/components/FormSelect/package.json +1 -1
  36. package/dist/dynamic/components/HelperText/package.json +1 -1
  37. package/dist/dynamic/components/Hero/package.json +1 -1
  38. package/dist/dynamic/components/Hint/package.json +1 -1
  39. package/dist/dynamic/components/Icon/package.json +1 -1
  40. package/dist/dynamic/components/InputGroup/package.json +1 -1
  41. package/dist/dynamic/components/JumpLinks/package.json +1 -1
  42. package/dist/dynamic/components/Label/package.json +1 -1
  43. package/dist/dynamic/components/List/package.json +1 -1
  44. package/dist/dynamic/components/LoginPage/package.json +1 -1
  45. package/dist/dynamic/components/Masthead/package.json +1 -1
  46. package/dist/dynamic/components/Menu/package.json +1 -1
  47. package/dist/dynamic/components/MenuToggle/package.json +1 -1
  48. package/dist/dynamic/components/Modal/package.json +1 -1
  49. package/dist/dynamic/components/MultipleFileUpload/package.json +1 -1
  50. package/dist/dynamic/components/Nav/package.json +1 -1
  51. package/dist/dynamic/components/NotificationBadge/package.json +1 -1
  52. package/dist/dynamic/components/NotificationDrawer/package.json +1 -1
  53. package/dist/dynamic/components/NumberInput/package.json +1 -1
  54. package/dist/dynamic/components/OverflowMenu/package.json +1 -1
  55. package/dist/dynamic/components/Page/package.json +1 -1
  56. package/dist/dynamic/components/Pagination/package.json +1 -1
  57. package/dist/dynamic/components/Panel/package.json +1 -1
  58. package/dist/dynamic/components/Popover/package.json +1 -1
  59. package/dist/dynamic/components/Progress/package.json +1 -1
  60. package/dist/dynamic/components/ProgressStepper/package.json +1 -1
  61. package/dist/dynamic/components/Radio/package.json +1 -1
  62. package/dist/dynamic/components/SearchInput/package.json +1 -1
  63. package/dist/dynamic/components/Select/package.json +1 -1
  64. package/dist/dynamic/components/Sidebar/package.json +1 -1
  65. package/dist/dynamic/components/SimpleList/package.json +1 -1
  66. package/dist/dynamic/components/Skeleton/package.json +1 -1
  67. package/dist/dynamic/components/SkipToContent/package.json +1 -1
  68. package/dist/dynamic/components/Slider/package.json +1 -1
  69. package/dist/dynamic/components/Spinner/package.json +1 -1
  70. package/dist/dynamic/components/Switch/package.json +1 -1
  71. package/dist/dynamic/components/Tabs/package.json +1 -1
  72. package/dist/dynamic/components/TextArea/package.json +1 -1
  73. package/dist/dynamic/components/TextInput/package.json +1 -1
  74. package/dist/dynamic/components/TextInputGroup/package.json +1 -1
  75. package/dist/dynamic/components/TimePicker/package.json +1 -1
  76. package/dist/dynamic/components/Timestamp/package.json +1 -1
  77. package/dist/dynamic/components/Title/package.json +1 -1
  78. package/dist/dynamic/components/ToggleGroup/package.json +1 -1
  79. package/dist/dynamic/components/Toolbar/package.json +1 -1
  80. package/dist/dynamic/components/Tooltip/package.json +1 -1
  81. package/dist/dynamic/components/TreeView/package.json +1 -1
  82. package/dist/dynamic/components/Truncate/package.json +1 -1
  83. package/dist/dynamic/components/Wizard/hooks/package.json +1 -1
  84. package/dist/dynamic/components/Wizard/package.json +1 -1
  85. package/dist/dynamic/deprecated/components/Chip/package.json +1 -1
  86. package/dist/dynamic/deprecated/components/DragDrop/package.json +1 -1
  87. package/dist/dynamic/deprecated/components/DualListSelector/package.json +1 -1
  88. package/dist/dynamic/deprecated/components/Modal/package.json +1 -1
  89. package/dist/dynamic/deprecated/components/Tile/package.json +1 -1
  90. package/dist/dynamic/deprecated/components/Wizard/package.json +1 -1
  91. package/dist/dynamic/deprecated/components/package.json +1 -1
  92. package/dist/dynamic/helpers/AnimationsProvider/AnimationsProvider/package.json +1 -1
  93. package/dist/dynamic/helpers/AnimationsProvider/package.json +1 -1
  94. package/dist/dynamic/helpers/FocusTrap/FocusTrap/package.json +1 -1
  95. package/dist/dynamic/helpers/GenerateId/GenerateId/package.json +1 -1
  96. package/dist/dynamic/helpers/KeyboardHandler/package.json +1 -1
  97. package/dist/dynamic/helpers/OUIA/ouia/package.json +1 -1
  98. package/dist/dynamic/helpers/Popper/Popper/package.json +1 -1
  99. package/dist/dynamic/helpers/SSRSafeIds/SSRSafeIds/package.json +1 -1
  100. package/dist/dynamic/helpers/constants/package.json +1 -1
  101. package/dist/dynamic/helpers/datetimeUtils/package.json +1 -1
  102. package/dist/dynamic/helpers/fileUtils/package.json +1 -1
  103. package/dist/dynamic/helpers/htmlConstants/package.json +1 -1
  104. package/dist/dynamic/helpers/inert/package.json +1 -1
  105. package/dist/dynamic/helpers/package.json +1 -1
  106. package/dist/dynamic/helpers/resizeObserver/package.json +1 -1
  107. package/dist/dynamic/helpers/typeUtils/package.json +1 -1
  108. package/dist/dynamic/helpers/useInterval/package.json +1 -1
  109. package/dist/dynamic/helpers/useIsomorphicLayout/package.json +1 -1
  110. package/dist/dynamic/helpers/useSSRSafeId/package.json +1 -1
  111. package/dist/dynamic/helpers/useUnmountEffect/package.json +1 -1
  112. package/dist/dynamic/helpers/util/package.json +1 -1
  113. package/dist/dynamic/layouts/Bullseye/package.json +1 -1
  114. package/dist/dynamic/layouts/Flex/package.json +1 -1
  115. package/dist/dynamic/layouts/Gallery/package.json +1 -1
  116. package/dist/dynamic/layouts/Grid/package.json +1 -1
  117. package/dist/dynamic/layouts/Level/package.json +1 -1
  118. package/dist/dynamic/layouts/Split/package.json +1 -1
  119. package/dist/dynamic/layouts/Stack/package.json +1 -1
  120. package/dist/dynamic/styles/package.json +1 -1
  121. package/dist/esm/components/Backdrop/Backdrop.d.ts +4 -0
  122. package/dist/esm/components/Backdrop/Backdrop.d.ts.map +1 -1
  123. package/dist/esm/components/Backdrop/Backdrop.js +4 -2
  124. package/dist/esm/components/Backdrop/Backdrop.js.map +1 -1
  125. package/dist/esm/components/Modal/Modal.d.ts +2 -0
  126. package/dist/esm/components/Modal/Modal.d.ts.map +1 -1
  127. package/dist/esm/components/Modal/Modal.js +2 -1
  128. package/dist/esm/components/Modal/Modal.js.map +1 -1
  129. package/dist/esm/components/Modal/ModalBox.d.ts +4 -0
  130. package/dist/esm/components/Modal/ModalBox.d.ts.map +1 -1
  131. package/dist/esm/components/Modal/ModalBox.js +2 -2
  132. package/dist/esm/components/Modal/ModalBox.js.map +1 -1
  133. package/dist/esm/components/Modal/ModalContent.d.ts +2 -0
  134. package/dist/esm/components/Modal/ModalContent.d.ts.map +1 -1
  135. package/dist/esm/components/Modal/ModalContent.js +40 -5
  136. package/dist/esm/components/Modal/ModalContent.js.map +1 -1
  137. package/dist/js/components/Backdrop/Backdrop.d.ts +4 -0
  138. package/dist/js/components/Backdrop/Backdrop.d.ts.map +1 -1
  139. package/dist/js/components/Backdrop/Backdrop.js +4 -2
  140. package/dist/js/components/Backdrop/Backdrop.js.map +1 -1
  141. package/dist/js/components/Modal/Modal.d.ts +2 -0
  142. package/dist/js/components/Modal/Modal.d.ts.map +1 -1
  143. package/dist/js/components/Modal/Modal.js +2 -1
  144. package/dist/js/components/Modal/Modal.js.map +1 -1
  145. package/dist/js/components/Modal/ModalBox.d.ts +4 -0
  146. package/dist/js/components/Modal/ModalBox.d.ts.map +1 -1
  147. package/dist/js/components/Modal/ModalBox.js +2 -2
  148. package/dist/js/components/Modal/ModalBox.js.map +1 -1
  149. package/dist/js/components/Modal/ModalContent.d.ts +2 -0
  150. package/dist/js/components/Modal/ModalContent.d.ts.map +1 -1
  151. package/dist/js/components/Modal/ModalContent.js +39 -4
  152. package/dist/js/components/Modal/ModalContent.js.map +1 -1
  153. package/dist/umd/assets/{output-Doo5KfbL.css → output-BPcxNyfB.css} +15654 -15654
  154. package/dist/umd/react-core.min.js +1 -1
  155. package/helpers/package.json +1 -1
  156. package/layouts/package.json +1 -1
  157. package/next/package.json +1 -1
  158. package/package.json +3 -3
  159. package/src/components/Backdrop/Backdrop.tsx +24 -5
  160. package/src/components/Backdrop/__tests__/Backdrop.test.tsx +35 -1
  161. package/src/components/Backdrop/__tests__/__snapshots__/Backdrop.test.tsx.snap +1 -1
  162. package/src/components/Modal/Modal.tsx +4 -1
  163. package/src/components/Modal/ModalBox.tsx +8 -0
  164. package/src/components/Modal/ModalContent.tsx +54 -4
  165. package/src/components/Modal/__tests__/Modal.test.tsx +14 -0
  166. package/src/components/Modal/__tests__/ModalContent.test.tsx +75 -1
  167. package/src/components/Modal/examples/Modal.md +12 -0
  168. package/src/components/Modal/examples/ModalNotAnimated.tsx +44 -0
  169. package/src/deprecated/components/Modal/__tests__/__snapshots__/ModalContent.test.tsx.snap +8 -8
@@ -1 +1 @@
1
- {"name":"@patternfly/react-core-helpers","main":"../dist/js/helpers/index.js","module":"../dist/esm/helpers/index.js","typings":"../dist/esm/helpers/index.d.ts","version":"6.7.0-prerelease.0","private":true}
1
+ {"name":"@patternfly/react-core-helpers","main":"../dist/js/helpers/index.js","module":"../dist/esm/helpers/index.js","typings":"../dist/esm/helpers/index.d.ts","version":"6.7.0-prerelease.1","private":true}
@@ -1 +1 @@
1
- {"name":"@patternfly/react-core-layouts","main":"../dist/js/layouts/index.js","module":"../dist/esm/layouts/index.js","typings":"../dist/esm/layouts/index.d.ts","version":"6.7.0-prerelease.0","private":true}
1
+ {"name":"@patternfly/react-core-layouts","main":"../dist/js/layouts/index.js","module":"../dist/esm/layouts/index.js","typings":"../dist/esm/layouts/index.d.ts","version":"6.7.0-prerelease.1","private":true}
package/next/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@patternfly/react-core-next","main":"../dist/js/next/index.js","module":"../dist/esm/next/index.js","typings":"../dist/esm/next/index.d.ts","version":"6.7.0-prerelease.0","private":true}
1
+ {"name":"@patternfly/react-core-next","main":"../dist/js/next/index.js","module":"../dist/esm/next/index.js","typings":"../dist/esm/next/index.d.ts","version":"6.7.0-prerelease.1","private":true}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "6.7.0-prerelease.1",
3
+ "version": "6.7.0-prerelease.2",
4
4
  "description": "This library provides a set of common React components for use with the PatternFly reference implementation.",
5
5
  "main": "dist/js/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -48,7 +48,7 @@
48
48
  "dependencies": {
49
49
  "@patternfly/react-icons": "^6.7.0-prerelease.0",
50
50
  "@patternfly/react-styles": "^6.7.0-prerelease.0",
51
- "@patternfly/react-tokens": "^6.7.0-prerelease.0",
51
+ "@patternfly/react-tokens": "^6.7.0-prerelease.1",
52
52
  "focus-trap": "7.6.6",
53
53
  "react-dropzone": "^14.3.5",
54
54
  "tslib": "^2.8.1"
@@ -63,5 +63,5 @@
63
63
  "react": "^17 || ^18 || ^19",
64
64
  "react-dom": "^17 || ^18 || ^19"
65
65
  },
66
- "gitHead": "7c85c1ea81ad07730b54c1e7ae5af218eecb0923"
66
+ "gitHead": "6a486021829bef4ba8c6a772c035b364ab5f0686"
67
67
  }
@@ -1,20 +1,39 @@
1
1
  import { css } from '@patternfly/react-styles';
2
2
  import styles from '@patternfly/react-styles/css/components/Backdrop/backdrop';
3
+ import { useHasAnimations } from '../../helpers';
3
4
 
4
5
  export interface BackdropProps extends React.HTMLProps<HTMLDivElement> {
5
6
  /** Content rendered inside the backdrop */
6
7
  children?: React.ReactNode;
7
8
  /** Additional classes added to the backdrop */
8
9
  className?: string;
10
+ /** Flag indicating whether animations are enabled. Animations are enabled by default. */
11
+ hasAnimations?: boolean;
12
+ /** Flag to show the backdrop when animations are enabled. Set to false while the backdrop remains mounted to play its exit transition. */
13
+ isVisible?: boolean;
9
14
  }
10
15
 
11
16
  export const Backdrop: React.FunctionComponent<BackdropProps> = ({
12
17
  children = null,
13
18
  className = '',
19
+ hasAnimations: hasAnimationsProp = true,
20
+ isVisible = true,
14
21
  ...props
15
- }: BackdropProps) => (
16
- <div {...props} className={css(styles.backdrop, className)}>
17
- {children}
18
- </div>
19
- );
22
+ }: BackdropProps) => {
23
+ const hasAnimations = useHasAnimations(hasAnimationsProp);
24
+
25
+ return (
26
+ <div
27
+ {...props}
28
+ className={css(
29
+ styles.backdrop,
30
+ hasAnimations && styles.modifiers.animate,
31
+ hasAnimations && isVisible && styles.modifiers.show,
32
+ className
33
+ )}
34
+ >
35
+ {children}
36
+ </div>
37
+ );
38
+ };
20
39
  Backdrop.displayName = 'Backdrop';
@@ -21,11 +21,45 @@ test(`Renders with the ${styles.backdrop}`, () => {
21
21
  expect(screen.getByText('Test')).toHaveClass(styles.backdrop);
22
22
  });
23
23
 
24
- test(`Renders with only the class ${styles.backdrop} by default`, () => {
24
+ test('Renders with animations enabled by default', () => {
25
25
  render(<Backdrop>Test</Backdrop>);
26
+ expect(screen.getByText('Test')).toHaveClass(styles.backdrop, styles.modifiers.animate, styles.modifiers.show, {
27
+ exact: true
28
+ });
29
+ });
30
+
31
+ test('Renders without animation classes when animations are explicitly disabled', () => {
32
+ render(<Backdrop hasAnimations={false}>Test</Backdrop>);
26
33
  expect(screen.getByText('Test')).toHaveClass(styles.backdrop, { exact: true });
27
34
  });
28
35
 
36
+ test('Renders as visible by default when animations are enabled', () => {
37
+ render(<Backdrop hasAnimations>Test</Backdrop>);
38
+ expect(screen.getByText('Test')).toHaveClass(styles.backdrop, styles.modifiers.animate, styles.modifiers.show, {
39
+ exact: true
40
+ });
41
+ });
42
+
43
+ test('Renders as hidden when animations are enabled and isVisible is false', () => {
44
+ render(
45
+ <Backdrop hasAnimations isVisible={false}>
46
+ Test
47
+ </Backdrop>
48
+ );
49
+ expect(screen.getByText('Test')).toHaveClass(styles.backdrop, styles.modifiers.animate, { exact: true });
50
+ });
51
+
52
+ test('Renders as visible when animations are enabled and isVisible is true', () => {
53
+ render(
54
+ <Backdrop hasAnimations isVisible>
55
+ Test
56
+ </Backdrop>
57
+ );
58
+ expect(screen.getByText('Test')).toHaveClass(styles.backdrop, styles.modifiers.animate, styles.modifiers.show, {
59
+ exact: true
60
+ });
61
+ });
62
+
29
63
  test('Renders with custom class name when className prop is passed', () => {
30
64
  render(<Backdrop className="test-class">Test</Backdrop>);
31
65
  expect(screen.getByText('Test')).toHaveClass('test-class');
@@ -3,7 +3,7 @@
3
3
  exports[`Matches the snapshot 1`] = `
4
4
  <DocumentFragment>
5
5
  <div
6
- class="pf-v6-c-backdrop"
6
+ class="pf-v6-c-backdrop pf-m-animate pf-m-show"
7
7
  >
8
8
  Backdrop
9
9
  </div>
@@ -53,6 +53,8 @@ export interface ModalProps extends React.HTMLProps<HTMLDivElement>, OUIAProps {
53
53
  ouiaId?: number | string;
54
54
  /** 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. */
55
55
  ouiaSafe?: boolean;
56
+ /** Flag indicating whether animations are enabled. Animations are enabled by default. */
57
+ hasAnimations?: boolean;
56
58
  }
57
59
 
58
60
  export enum ModalVariant {
@@ -78,7 +80,8 @@ class Modal extends Component<ModalProps, ModalState> {
78
80
  variant: 'default',
79
81
  appendTo: () => document.body,
80
82
  ouiaSafe: true,
81
- position: 'default'
83
+ position: 'default',
84
+ hasAnimations: true
82
85
  };
83
86
 
84
87
  constructor(props: ModalProps) {
@@ -19,6 +19,10 @@ export interface ModalBoxProps extends React.HTMLProps<HTMLDivElement> {
19
19
  positionOffset?: string;
20
20
  /** Variant of the modal. */
21
21
  variant?: 'small' | 'medium' | 'large' | 'default';
22
+ /** Flag indicating whether animations are enabled. */
23
+ hasAnimations?: boolean;
24
+ /** Flag to show the modal. */
25
+ isOpen?: boolean;
22
26
  }
23
27
 
24
28
  export const ModalBox: React.FunctionComponent<ModalBoxProps> = ({
@@ -31,6 +35,8 @@ export const ModalBox: React.FunctionComponent<ModalBoxProps> = ({
31
35
  'aria-label': ariaLabel,
32
36
  'aria-describedby': ariaDescribedby,
33
37
  style,
38
+ isOpen,
39
+ hasAnimations,
34
40
  ...props
35
41
  }: ModalBoxProps) => {
36
42
  if (positionOffset) {
@@ -46,6 +52,8 @@ export const ModalBox: React.FunctionComponent<ModalBoxProps> = ({
46
52
  aria-modal="true"
47
53
  className={css(
48
54
  styles.modalBox,
55
+ hasAnimations && styles.modifiers.animate,
56
+ hasAnimations && isOpen === true && styles.modifiers.open,
49
57
  className,
50
58
  position === 'top' && styles.modifiers.alignTop,
51
59
  variant === 'large' && styles.modifiers.lg,
@@ -1,11 +1,14 @@
1
+ import { useEffect, useState } from 'react';
1
2
  import { FocusTrap } from '../../helpers';
2
3
  import bullsEyeStyles from '@patternfly/react-styles/css/layouts/Bullseye/bullseye';
3
4
  import { css } from '@patternfly/react-styles';
4
- import { getOUIAProps, OUIAProps } from '../../helpers';
5
+ import { getOUIAProps, OUIAProps, useHasAnimations } from '../../helpers';
5
6
  import { Backdrop } from '../Backdrop';
6
7
  import { ModalBoxCloseButton } from './ModalBoxCloseButton';
7
8
  import { ModalBox } from './ModalBox';
8
9
 
10
+ const transitionEndFallbackDelay = 300;
11
+
9
12
  export interface ModalContentProps extends OUIAProps {
10
13
  /** Id to use for the modal box description. This should match the ModalHeader labelId or descriptorId. */
11
14
  'aria-describedby'?: string;
@@ -49,6 +52,8 @@ export interface ModalContentProps extends OUIAProps {
49
52
  ouiaId?: number | string;
50
53
  /** 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. */
51
54
  ouiaSafe?: boolean;
55
+ /** Flag indicating whether animations are enabled. */
56
+ hasAnimations?: boolean;
52
57
  }
53
58
 
54
59
  export const ModalContent: React.FunctionComponent<ModalContentProps> = ({
@@ -72,9 +77,30 @@ export const ModalContent: React.FunctionComponent<ModalContentProps> = ({
72
77
  ouiaSafe = true,
73
78
  elementToFocus,
74
79
  focusTrapId,
80
+ hasAnimations: hasAnimationsProp,
75
81
  ...props
76
82
  }: ModalContentProps) => {
77
- if (!isOpen) {
83
+ const hasAnimations = useHasAnimations(hasAnimationsProp);
84
+ // Keeps the modal in the DOM while the close animation runs. When animations are enabled we defer
85
+ // unmounting until the backdrop's transition ends (see onTransitionEnd below) instead of removing
86
+ // it immediately when isOpen becomes false.
87
+ const [isRendered, setIsRendered] = useState(isOpen);
88
+
89
+ useEffect(() => {
90
+ if (isOpen) {
91
+ setIsRendered(true);
92
+ } else if (!isRendered) {
93
+ return;
94
+ } else if (!hasAnimations) {
95
+ setIsRendered(false);
96
+ } else {
97
+ // Ensure the modal is removed if CSS transitions are disabled or transitionend does not fire.
98
+ const transitionEndFallback = window.setTimeout(() => setIsRendered(false), transitionEndFallbackDelay);
99
+ return () => window.clearTimeout(transitionEndFallback);
100
+ }
101
+ }, [isOpen, hasAnimations, isRendered]);
102
+
103
+ if (!isRendered) {
78
104
  return null;
79
105
  }
80
106
 
@@ -91,6 +117,8 @@ export const ModalContent: React.FunctionComponent<ModalContentProps> = ({
91
117
  const modalBox = (
92
118
  <ModalBox
93
119
  className={css(className)}
120
+ isOpen={isOpen}
121
+ hasAnimations={hasAnimations}
94
122
  variant={variant}
95
123
  position={position}
96
124
  positionOffset={positionOffset}
@@ -113,10 +141,32 @@ export const ModalContent: React.FunctionComponent<ModalContentProps> = ({
113
141
  {children}
114
142
  </ModalBox>
115
143
  );
144
+ let focusTrapActive = !disableFocusTrap;
145
+ if (hasAnimations) {
146
+ focusTrapActive = !disableFocusTrap && isOpen;
147
+ }
148
+
116
149
  return (
117
- <Backdrop className={css(backdropClassName)} id={backdropId}>
150
+ <Backdrop
151
+ className={css(backdropClassName)}
152
+ id={backdropId}
153
+ hasAnimations={hasAnimations}
154
+ isVisible={isOpen}
155
+ aria-hidden={hasAnimations && !isOpen ? true : undefined}
156
+ onTransitionEnd={
157
+ hasAnimations
158
+ ? (event) => {
159
+ // Only unmount once the backdrop's own closing transition finishes. Guarding on the
160
+ // target prevents bubbled transitions from child elements from triggering this early.
161
+ if (!isOpen && event.target === event.currentTarget) {
162
+ setIsRendered(false);
163
+ }
164
+ }
165
+ : undefined
166
+ }
167
+ >
118
168
  <FocusTrap
119
- active={!disableFocusTrap}
169
+ active={focusTrapActive}
120
170
  focusTrapOptions={{
121
171
  clickOutsideDeactivates: true,
122
172
  tabbableOptions: { displayCheck: 'none' },
@@ -111,6 +111,20 @@ describe('Modal', () => {
111
111
  expect(document.body).toHaveClass(css(styles.backdropOpen));
112
112
  });
113
113
 
114
+ test('modal has animations enabled by default', () => {
115
+ render(<Modal {...props} isOpen />);
116
+ const backdrop = screen.getByText('modal content').closest(`.${styles.backdrop}`);
117
+
118
+ expect(backdrop).toHaveClass(styles.modifiers.animate, styles.modifiers.show);
119
+ });
120
+
121
+ test('modal animations can be explicitly disabled', () => {
122
+ render(<Modal {...props} isOpen hasAnimations={false} />);
123
+ const backdrop = screen.getByText('modal content').closest(`.${styles.backdrop}`);
124
+
125
+ expect(backdrop).not.toHaveClass(styles.modifiers.animate, styles.modifiers.show);
126
+ });
127
+
114
128
  test('modal has no body backdropOpen class when not open', () => {
115
129
  render(<Modal {...props} />);
116
130
  expect(document.body).not.toHaveClass(css(styles.backdropOpen));
@@ -1,4 +1,4 @@
1
- import { render, screen } from '@testing-library/react';
1
+ import { act, render, screen } from '@testing-library/react';
2
2
 
3
3
  import { ModalContent } from '../ModalContent';
4
4
 
@@ -59,3 +59,77 @@ test('Modal content can add id to focus trap correctly for use with dropdowns',
59
59
  'pf-v6-l-bullseye'
60
60
  );
61
61
  });
62
+
63
+ test('Modal content is hidden from assistive technologies during its closing animation', () => {
64
+ const { rerender } = render(
65
+ <ModalContent isOpen hasAnimations backdropId="backdropId" {...modalContentProps}>
66
+ This is a ModalBox header
67
+ </ModalContent>
68
+ );
69
+ const backdrop = document.getElementById('backdropId');
70
+
71
+ expect(backdrop).not.toHaveAttribute('aria-hidden');
72
+
73
+ rerender(
74
+ <ModalContent isOpen={false} hasAnimations backdropId="backdropId" {...modalContentProps}>
75
+ This is a ModalBox header
76
+ </ModalContent>
77
+ );
78
+ expect(backdrop).toHaveAttribute('aria-hidden', 'true');
79
+
80
+ rerender(
81
+ <ModalContent isOpen hasAnimations backdropId="backdropId" {...modalContentProps}>
82
+ This is a ModalBox header
83
+ </ModalContent>
84
+ );
85
+ expect(backdrop).not.toHaveAttribute('aria-hidden');
86
+ });
87
+
88
+ test('Modal content unmounts if its closing transition does not end', () => {
89
+ jest.useFakeTimers();
90
+ try {
91
+ const { rerender } = render(
92
+ <ModalContent isOpen hasAnimations backdropId="backdropId" {...modalContentProps}>
93
+ This is a ModalBox header
94
+ </ModalContent>
95
+ );
96
+
97
+ rerender(
98
+ <ModalContent isOpen={false} hasAnimations backdropId="backdropId" {...modalContentProps}>
99
+ This is a ModalBox header
100
+ </ModalContent>
101
+ );
102
+ expect(document.getElementById('backdropId')).toBeInTheDocument();
103
+
104
+ act(() => jest.runOnlyPendingTimers());
105
+ expect(document.getElementById('backdropId')).not.toBeInTheDocument();
106
+ } finally {
107
+ jest.useRealTimers();
108
+ }
109
+ });
110
+
111
+ test('Modal content remains mounted during close when reduced motion is preferred', () => {
112
+ const matchMedia = window.matchMedia;
113
+ window.matchMedia = jest.fn().mockReturnValue({ matches: true } as MediaQueryList);
114
+ jest.useFakeTimers();
115
+ try {
116
+ const { rerender } = render(
117
+ <ModalContent isOpen hasAnimations backdropId="backdropId" {...modalContentProps}>
118
+ This is a ModalBox header
119
+ </ModalContent>
120
+ );
121
+
122
+ rerender(
123
+ <ModalContent isOpen={false} hasAnimations backdropId="backdropId" {...modalContentProps}>
124
+ This is a ModalBox header
125
+ </ModalContent>
126
+ );
127
+ expect(document.getElementById('backdropId')).toBeInTheDocument();
128
+
129
+ act(() => jest.runOnlyPendingTimers());
130
+ expect(document.getElementById('backdropId')).not.toBeInTheDocument();
131
+ } finally {
132
+ jest.useRealTimers();
133
+ window.matchMedia = matchMedia;
134
+ }
135
+ });
@@ -12,6 +12,10 @@ import RhUiAttentionBellFillIcon from '@patternfly/react-icons/dist/esm/icons/rh
12
12
  import RhUiQuestionMarkCircleIcon from '@patternfly/react-icons/dist/esm/icons/rh-ui-question-mark-circle-icon';
13
13
  import formStyles from '@patternfly/react-styles/css/components/Form/form';
14
14
 
15
+ When using the Modal component, it is recommended to control it via the `isOpen` property rather than conditionally rendering the Modal.
16
+
17
+ Conditional rendering can potentially lead to unforeseen issues, especially with animations enabled.
18
+
15
19
  ## Examples
16
20
 
17
21
  ### Basic modals
@@ -154,3 +158,11 @@ To customize which element inside the modal receives focus when initially opened
154
158
  ```ts file="./ModalCustomFocus.tsx"
155
159
 
156
160
  ```
161
+
162
+ ### Non animated modal
163
+
164
+ To explicitly disable animations, set the `hasAnimations` property to `false` on the modal.
165
+
166
+ ```ts file="./ModalNotAnimated.tsx"
167
+
168
+ ```
@@ -0,0 +1,44 @@
1
+ import { Fragment, useState } from 'react';
2
+ import { Button, Modal, ModalHeader, ModalBody, ModalFooter, ModalVariant } from '@patternfly/react-core';
3
+
4
+ export const ModalNotAnimated: React.FunctionComponent = () => {
5
+ const [isModalOpen, setIsModalOpen] = useState(false);
6
+
7
+ const handleModalToggle = (_event: KeyboardEvent | React.MouseEvent) => {
8
+ setIsModalOpen((prevIsModalOpen) => !prevIsModalOpen);
9
+ };
10
+
11
+ return (
12
+ <Fragment>
13
+ <Button variant="primary" onClick={handleModalToggle}>
14
+ Show no animations modal
15
+ </Button>
16
+ <Modal
17
+ hasAnimations={false}
18
+ variant={ModalVariant.large}
19
+ isOpen={isModalOpen}
20
+ onClose={handleModalToggle}
21
+ aria-labelledby="modal-not-animated-label"
22
+ aria-describedby="modal-not-animated-description"
23
+ elementToFocus="#modal-not-animated-confirm-button"
24
+ >
25
+ <ModalHeader title="Not Animated Modal Header" labelId="modal-not-animated-label" />
26
+ <ModalBody id="modal-not-animated-description">
27
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
28
+ magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
29
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
30
+ pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id
31
+ est laborum.
32
+ </ModalBody>
33
+ <ModalFooter>
34
+ <Button id="modal-not-animated-confirm-button" key="confirm" variant="primary" onClick={handleModalToggle}>
35
+ Confirm
36
+ </Button>
37
+ <Button key="cancel" variant="link" onClick={handleModalToggle}>
38
+ Cancel
39
+ </Button>
40
+ </ModalFooter>
41
+ </Modal>
42
+ </Fragment>
43
+ );
44
+ };
@@ -3,7 +3,7 @@
3
3
  exports[`Modal Content Test description 1`] = `
4
4
  <DocumentFragment>
5
5
  <div
6
- class="pf-v6-c-backdrop"
6
+ class="pf-v6-c-backdrop pf-m-animate pf-m-show"
7
7
  >
8
8
  <div
9
9
  class="pf-v6-l-bullseye"
@@ -82,7 +82,7 @@ exports[`Modal Content Test description 1`] = `
82
82
  exports[`Modal Content Test isOpen 1`] = `
83
83
  <DocumentFragment>
84
84
  <div
85
- class="pf-v6-c-backdrop"
85
+ class="pf-v6-c-backdrop pf-m-animate pf-m-show"
86
86
  >
87
87
  <div
88
88
  class="pf-v6-l-bullseye"
@@ -156,7 +156,7 @@ exports[`Modal Content Test isOpen 1`] = `
156
156
  exports[`Modal Content Test only body 1`] = `
157
157
  <DocumentFragment>
158
158
  <div
159
- class="pf-v6-c-backdrop"
159
+ class="pf-v6-c-backdrop pf-m-animate pf-m-show"
160
160
  >
161
161
  <div
162
162
  class="pf-v6-l-bullseye"
@@ -230,7 +230,7 @@ exports[`Modal Content Test only body 1`] = `
230
230
  exports[`Modal Content Test with footer 1`] = `
231
231
  <DocumentFragment>
232
232
  <div
233
- class="pf-v6-c-backdrop"
233
+ class="pf-v6-c-backdrop pf-m-animate pf-m-show"
234
234
  >
235
235
  <div
236
236
  class="pf-v6-l-bullseye"
@@ -309,7 +309,7 @@ exports[`Modal Content Test with footer 1`] = `
309
309
  exports[`Modal Content Test with onclose 1`] = `
310
310
  <DocumentFragment>
311
311
  <div
312
- class="pf-v6-c-backdrop"
312
+ class="pf-v6-c-backdrop pf-m-animate pf-m-show"
313
313
  >
314
314
  <div
315
315
  class="pf-v6-l-bullseye"
@@ -388,7 +388,7 @@ exports[`Modal Content Test with onclose 1`] = `
388
388
  exports[`Modal Content test without footer 1`] = `
389
389
  <DocumentFragment>
390
390
  <div
391
- class="pf-v6-c-backdrop"
391
+ class="pf-v6-c-backdrop pf-m-animate pf-m-show"
392
392
  >
393
393
  <div
394
394
  class="pf-v6-l-bullseye"
@@ -462,7 +462,7 @@ exports[`Modal Content test without footer 1`] = `
462
462
  exports[`Modal Test with custom footer 1`] = `
463
463
  <DocumentFragment>
464
464
  <div
465
- class="pf-v6-c-backdrop"
465
+ class="pf-v6-c-backdrop pf-m-animate pf-m-show"
466
466
  >
467
467
  <div
468
468
  class="pf-v6-l-bullseye"
@@ -545,7 +545,7 @@ exports[`Modal Test with custom footer 1`] = `
545
545
  exports[`Modal Test with custom header 1`] = `
546
546
  <DocumentFragment>
547
547
  <div
548
- class="pf-v6-c-backdrop"
548
+ class="pf-v6-c-backdrop pf-m-animate pf-m-show"
549
549
  >
550
550
  <div
551
551
  class="pf-v6-l-bullseye"