@patternfly/react-core 6.6.1-prerelease.3 → 6.6.1-prerelease.5

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 (171) hide show
  1. package/CHANGELOG.md +16 -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/dynamic-modules.json +2 -0
  122. package/dist/esm/components/Modal/Modal.d.ts +2 -0
  123. package/dist/esm/components/Modal/Modal.d.ts.map +1 -1
  124. package/dist/esm/components/Modal/Modal.js +39 -7
  125. package/dist/esm/components/Modal/Modal.js.map +1 -1
  126. package/dist/esm/components/Page/Page.d.ts +4 -1
  127. package/dist/esm/components/Page/Page.d.ts.map +1 -1
  128. package/dist/esm/components/Page/Page.js.map +1 -1
  129. package/dist/esm/components/Page/PageHeader.d.ts +11 -0
  130. package/dist/esm/components/Page/PageHeader.d.ts.map +1 -0
  131. package/dist/esm/components/Page/PageHeader.js +11 -0
  132. package/dist/esm/components/Page/PageHeader.js.map +1 -0
  133. package/dist/esm/components/Page/index.d.ts +1 -0
  134. package/dist/esm/components/Page/index.d.ts.map +1 -1
  135. package/dist/esm/components/Page/index.js +1 -0
  136. package/dist/esm/components/Page/index.js.map +1 -1
  137. package/dist/js/components/Modal/Modal.d.ts +2 -0
  138. package/dist/js/components/Modal/Modal.d.ts.map +1 -1
  139. package/dist/js/components/Modal/Modal.js +39 -7
  140. package/dist/js/components/Modal/Modal.js.map +1 -1
  141. package/dist/js/components/Page/Page.d.ts +4 -1
  142. package/dist/js/components/Page/Page.d.ts.map +1 -1
  143. package/dist/js/components/Page/Page.js.map +1 -1
  144. package/dist/js/components/Page/PageHeader.d.ts +11 -0
  145. package/dist/js/components/Page/PageHeader.d.ts.map +1 -0
  146. package/dist/js/components/Page/PageHeader.js +15 -0
  147. package/dist/js/components/Page/PageHeader.js.map +1 -0
  148. package/dist/js/components/Page/index.d.ts +1 -0
  149. package/dist/js/components/Page/index.d.ts.map +1 -1
  150. package/dist/js/components/Page/index.js +1 -0
  151. package/dist/js/components/Page/index.js.map +1 -1
  152. package/dist/umd/assets/{output-eXFa6Wof.css → output-J0Cx1pi_.css} +19765 -19687
  153. package/dist/umd/react-core.min.js +2 -2
  154. package/helpers/package.json +1 -1
  155. package/layouts/package.json +1 -1
  156. package/next/package.json +1 -1
  157. package/package.json +6 -6
  158. package/src/components/List/__tests__/__snapshots__/ListItem.test.tsx.snap +1 -3
  159. package/src/components/Modal/Modal.tsx +42 -7
  160. package/src/components/Modal/__tests__/Modal.test.tsx +152 -0
  161. package/src/components/Nav/__tests__/__snapshots__/Nav.test.tsx.snap +0 -1
  162. package/src/components/Page/Page.tsx +4 -1
  163. package/src/components/Page/PageHeader.tsx +28 -0
  164. package/src/components/Page/__tests__/Page.test.tsx +13 -0
  165. package/src/components/Page/__tests__/PageHeader.test.tsx +33 -0
  166. package/src/components/Page/examples/Page.md +19 -2
  167. package/src/components/Page/examples/PageHeaderContent.tsx +19 -0
  168. package/src/components/Page/index.ts +1 -0
  169. package/src/components/SearchInput/__tests__/__snapshots__/SearchInput.test.tsx.snap +2 -8
  170. package/src/components/ToggleGroup/__tests__/__snapshots__/ToggleGroupItemElement.test.tsx.snap +1 -3
  171. package/src/layouts/Flex/__tests__/__snapshots__/Flex.test.tsx.snap +3 -9
@@ -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.6.1-prerelease.2","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.6.1-prerelease.4","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.6.1-prerelease.2","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.6.1-prerelease.4","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.6.1-prerelease.2","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.6.1-prerelease.4","private":true}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "6.6.1-prerelease.3",
3
+ "version": "6.6.1-prerelease.5",
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",
@@ -46,15 +46,15 @@
46
46
  "subpaths": "node ../../scripts/exportSubpaths.mjs --config subpaths.config.json"
47
47
  },
48
48
  "dependencies": {
49
- "@patternfly/react-icons": "^6.6.1-prerelease.0",
50
- "@patternfly/react-styles": "^6.6.1-prerelease.0",
51
- "@patternfly/react-tokens": "^6.6.1-prerelease.0",
49
+ "@patternfly/react-icons": "^6.6.1-prerelease.1",
50
+ "@patternfly/react-styles": "^6.6.1-prerelease.1",
51
+ "@patternfly/react-tokens": "^6.6.1-prerelease.1",
52
52
  "focus-trap": "7.6.6",
53
53
  "react-dropzone": "^14.3.5",
54
54
  "tslib": "^2.8.1"
55
55
  },
56
56
  "devDependencies": {
57
- "@patternfly/patternfly": "6.6.0-prerelease.20",
57
+ "@patternfly/patternfly": "6.6.0-prerelease.39",
58
58
  "case-anything": "^3.1.2",
59
59
  "css": "^3.0.0",
60
60
  "fs-extra": "^11.3.3"
@@ -63,5 +63,5 @@
63
63
  "react": "^17 || ^18 || ^19",
64
64
  "react-dom": "^17 || ^18 || ^19"
65
65
  },
66
- "gitHead": "f20f0738c88cf45f51cdae7239b92ee6aaa26510"
66
+ "gitHead": "a01cd6377b953ae8f01eb2dc632311a2373dfeac"
67
67
  }
@@ -23,9 +23,7 @@ exports[`Matches snapshot with icon 1`] = `
23
23
 
24
24
  exports[`Matches snapshot without icon 1`] = `
25
25
  <DocumentFragment>
26
- <li
27
- class=""
28
- >
26
+ <li>
29
27
  <span>
30
28
  List item content
31
29
  </span>
@@ -69,6 +69,7 @@ interface ModalState {
69
69
  class Modal extends Component<ModalProps, ModalState> {
70
70
  static displayName = 'Modal';
71
71
  static currentId = 0;
72
+ static openModalStacks: Map<HTMLElement, string[]> = new Map();
72
73
  boxId = '';
73
74
  backdropId = '';
74
75
 
@@ -106,16 +107,46 @@ class Modal extends Component<ModalProps, ModalState> {
106
107
  return appendTo || document.body;
107
108
  };
108
109
 
110
+ static getStackForTarget(target: HTMLElement): string[] {
111
+ if (!Modal.openModalStacks.has(target)) {
112
+ Modal.openModalStacks.set(target, []);
113
+ }
114
+ return Modal.openModalStacks.get(target)!;
115
+ }
116
+
109
117
  toggleSiblingsFromScreenReaders = (hide: boolean) => {
110
118
  const { appendTo } = this.props;
111
119
  const target: HTMLElement = this.getElement(appendTo);
112
- const bodyChildren = target.children;
113
- for (const child of Array.from(bodyChildren)) {
114
- const isPopperElement = child.hasAttribute('data-popper-placement');
115
- if (child.id !== this.backdropId && !isPopperElement) {
116
- hide ? child.setAttribute('aria-hidden', '' + hide) : child.removeAttribute('aria-hidden');
120
+
121
+ if (hide) {
122
+ const stack = Modal.getStackForTarget(target);
123
+ if (stack.indexOf(this.backdropId) === -1) {
124
+ stack.push(this.backdropId);
125
+ }
126
+ } else {
127
+ const stack = Modal.openModalStacks.get(target);
128
+ if (!stack) {
129
+ return;
130
+ }
131
+ const idx = stack.indexOf(this.backdropId);
132
+ if (idx !== -1) {
133
+ stack.splice(idx, 1);
134
+ }
135
+ if (stack.length === 0) {
136
+ Modal.openModalStacks.delete(target);
117
137
  }
118
138
  }
139
+
140
+ const stack = Modal.openModalStacks.get(target);
141
+ const activeBackdropId = stack?.length ? stack[stack.length - 1] : null;
142
+
143
+ for (const child of Array.from(target.children)) {
144
+ if (child.hasAttribute('data-popper-placement')) {
145
+ continue;
146
+ }
147
+ const shouldHide = activeBackdropId && child.id !== activeBackdropId;
148
+ shouldHide ? child.setAttribute('aria-hidden', 'true') : child.removeAttribute('aria-hidden');
149
+ }
119
150
  };
120
151
 
121
152
  isEmpty = (value: string | null | undefined) => value === null || value === undefined || value === '';
@@ -140,8 +171,10 @@ class Modal extends Component<ModalProps, ModalState> {
140
171
  this.toggleSiblingsFromScreenReaders(true);
141
172
  } else {
142
173
  if (prevProps.isOpen !== this.props.isOpen) {
143
- target.classList.remove(css(styles.backdropOpen));
144
174
  this.toggleSiblingsFromScreenReaders(false);
175
+ if (!Modal.openModalStacks.has(target)) {
176
+ target.classList.remove(css(styles.backdropOpen));
177
+ }
145
178
  }
146
179
  }
147
180
  }
@@ -150,8 +183,10 @@ class Modal extends Component<ModalProps, ModalState> {
150
183
  const { appendTo } = this.props;
151
184
  const target: HTMLElement = this.getElement(appendTo);
152
185
  target.removeEventListener('keydown', this.handleEscKeyClick, false);
153
- target.classList.remove(css(styles.backdropOpen));
154
186
  this.toggleSiblingsFromScreenReaders(false);
187
+ if (!Modal.openModalStacks.has(target)) {
188
+ target.classList.remove(css(styles.backdropOpen));
189
+ }
155
190
  }
156
191
 
157
192
  render() {
@@ -64,7 +64,28 @@ const ModalWithAdjacentModal = () => {
64
64
  );
65
65
  };
66
66
 
67
+ const MultipleOpenModals = () => {
68
+ const [isFirstOpen, setIsFirstOpen] = useState(true);
69
+ const [isSecondOpen, setIsSecondOpen] = useState(false);
70
+
71
+ return (
72
+ <>
73
+ <aside>Aside sibling</aside>
74
+ <Modal isOpen={isFirstOpen} appendTo={target} onClose={() => setIsFirstOpen(false)} aria-label="First modal">
75
+ <button onClick={() => setIsSecondOpen(true)}>Open second modal</button>
76
+ </Modal>
77
+ <Modal isOpen={isSecondOpen} appendTo={target} onClose={() => setIsSecondOpen(false)} aria-label="Second modal">
78
+ Second modal content
79
+ </Modal>
80
+ </>
81
+ );
82
+ };
83
+
67
84
  describe('Modal', () => {
85
+ beforeEach(() => {
86
+ Modal.openModalStacks = new Map();
87
+ });
88
+
68
89
  test('Modal creates a container element once for div', () => {
69
90
  render(<Modal {...props} />);
70
91
  expect(document.createElement).toHaveBeenCalledWith('div');
@@ -181,4 +202,135 @@ describe('Modal', () => {
181
202
  'pf-v6-l-bullseye'
182
203
  );
183
204
  });
205
+
206
+ test('backdropOpen class remains when closing one of multiple open modals', async () => {
207
+ const user = userEvent.setup();
208
+
209
+ render(<MultipleOpenModals />, { container: document.body.appendChild(target) });
210
+
211
+ await user.click(screen.getByRole('button', { name: 'Open second modal' }));
212
+
213
+ expect(target).toHaveClass(css(styles.backdropOpen));
214
+
215
+ const closeButtons = screen.getAllByRole('button', { name: 'Close', hidden: true });
216
+ await user.click(closeButtons[closeButtons.length - 1]);
217
+
218
+ expect(target).toHaveClass(css(styles.backdropOpen));
219
+ });
220
+
221
+ test('backdropOpen class is removed when all modals are closed', async () => {
222
+ const user = userEvent.setup();
223
+
224
+ render(<MultipleOpenModals />, { container: document.body.appendChild(target) });
225
+
226
+ await user.click(screen.getByRole('button', { name: 'Open second modal' }));
227
+
228
+ const closeButtons = screen.getAllByRole('button', { name: 'Close', hidden: true });
229
+ await user.click(closeButtons[closeButtons.length - 1]);
230
+ await user.click(screen.getByRole('button', { name: 'Close' }));
231
+
232
+ expect(target).not.toHaveClass(css(styles.backdropOpen));
233
+ });
234
+
235
+ test('only the most recent modal does not have aria-hidden when multiple modals are open', async () => {
236
+ const user = userEvent.setup();
237
+
238
+ render(<MultipleOpenModals />, { container: document.body.appendChild(target) });
239
+
240
+ const firstBackdrop = screen.getByLabelText('First modal').closest('[class*="backdrop"]');
241
+
242
+ await user.click(screen.getByRole('button', { name: 'Open second modal' }));
243
+
244
+ const secondBackdrop = screen.getByLabelText('Second modal').closest('[class*="backdrop"]');
245
+
246
+ expect(firstBackdrop).toHaveAttribute('aria-hidden', 'true');
247
+ expect(secondBackdrop).not.toHaveAttribute('aria-hidden');
248
+ });
249
+
250
+ test('closing the active modal reveals the previous modal', async () => {
251
+ const user = userEvent.setup();
252
+
253
+ render(<MultipleOpenModals />, { container: document.body.appendChild(target) });
254
+
255
+ await user.click(screen.getByRole('button', { name: 'Open second modal' }));
256
+
257
+ const firstBackdrop = screen
258
+ .getByLabelText('First modal', { selector: '[role="dialog"]' })
259
+ .closest('[class*="backdrop"]');
260
+
261
+ expect(firstBackdrop).toHaveAttribute('aria-hidden', 'true');
262
+
263
+ const closeButtons = screen.getAllByRole('button', { name: 'Close', hidden: true });
264
+ await user.click(closeButtons[closeButtons.length - 1]);
265
+
266
+ expect(firstBackdrop).not.toHaveAttribute('aria-hidden');
267
+ });
268
+
269
+ test('modals with different appendTo targets have independent stacks', async () => {
270
+ const user = userEvent.setup();
271
+ const targetA = document.createElement('div');
272
+ const targetB = document.createElement('div');
273
+ document.body.appendChild(targetA);
274
+ document.body.appendChild(targetB);
275
+
276
+ const siblingA = document.createElement('aside');
277
+ siblingA.textContent = 'Sibling A';
278
+ targetA.appendChild(siblingA);
279
+
280
+ const siblingB = document.createElement('aside');
281
+ siblingB.textContent = 'Sibling B';
282
+ targetB.appendChild(siblingB);
283
+
284
+ const DistinctTargetModals = () => {
285
+ const [isAOpen, setIsAOpen] = useState(true);
286
+ const [isBOpen, setIsBOpen] = useState(true);
287
+
288
+ return (
289
+ <>
290
+ <Modal isOpen={isAOpen} appendTo={targetA} onClose={() => setIsAOpen(false)} aria-label="Modal A">
291
+ Modal A content
292
+ </Modal>
293
+ <Modal isOpen={isBOpen} appendTo={targetB} onClose={() => setIsBOpen(false)} aria-label="Modal B">
294
+ Modal B content
295
+ </Modal>
296
+ </>
297
+ );
298
+ };
299
+
300
+ render(<DistinctTargetModals />);
301
+
302
+ expect(siblingA).toHaveAttribute('aria-hidden', 'true');
303
+ expect(siblingB).toHaveAttribute('aria-hidden', 'true');
304
+ expect(targetA).toHaveClass(css(styles.backdropOpen));
305
+ expect(targetB).toHaveClass(css(styles.backdropOpen));
306
+
307
+ const closeButtons = screen.getAllByRole('button', { name: 'Close', hidden: true });
308
+ await user.click(closeButtons[1]);
309
+
310
+ expect(targetB).not.toHaveClass(css(styles.backdropOpen));
311
+ expect(siblingB).not.toHaveAttribute('aria-hidden');
312
+
313
+ expect(targetA).toHaveClass(css(styles.backdropOpen));
314
+ expect(siblingA).toHaveAttribute('aria-hidden', 'true');
315
+
316
+ document.body.removeChild(targetA);
317
+ document.body.removeChild(targetB);
318
+ });
319
+
320
+ test('unmounting a never-opened modal with a custom target does not leak a stack entry', () => {
321
+ const customTarget = document.createElement('div');
322
+ document.body.appendChild(customTarget);
323
+
324
+ const { unmount } = render(
325
+ <Modal isOpen={false} appendTo={customTarget} onClose={() => {}}>
326
+ Never opened
327
+ </Modal>
328
+ );
329
+
330
+ unmount();
331
+
332
+ expect(Modal.openModalStacks.has(customTarget)).toBe(false);
333
+
334
+ document.body.removeChild(customTarget);
335
+ });
184
336
  });
@@ -1017,7 +1017,6 @@ exports[`Nav Nav List with flyout 1`] = `
1017
1017
  </li>
1018
1018
  </ul>
1019
1019
  <div
1020
- class=""
1021
1020
  data-popper-escaped="true"
1022
1021
  data-popper-placement="right-start"
1023
1022
  data-popper-reference-hidden="true"
@@ -28,7 +28,10 @@ export interface PageProps extends React.HTMLProps<HTMLDivElement> {
28
28
  * will handle toggling the visibility of the text in individual isDocked components.
29
29
  */
30
30
  isDockTextExpanded?: boolean;
31
- /** The horizontal masthead content (e.g. <Masthead />). When using the docked variant, this content will only render at mobile viewports. */
31
+ /** The horizontal masthead content (e.g. <Masthead /> or <PageHeader />). PageHeader is an alternative to Masthead
32
+ * and should only be used to wrap custom header content. When using the docked variant, this content will only render at
33
+ * mobile viewports.
34
+ */
32
35
  masthead?: React.ReactNode;
33
36
  /** @beta Content to render in the vertical dock when variant of docked is used. At mobile viewports, this content will be replaced with the content passed to masthead. */
34
37
  dockContent?: React.ReactNode;
@@ -0,0 +1,28 @@
1
+ import styles from '@patternfly/react-styles/css/components/Page/page';
2
+ import { css } from '@patternfly/react-styles';
3
+
4
+ export interface PageHeaderProps extends React.HTMLProps<HTMLElement> {
5
+ /** Content rendered inside the page header. This should be custom header content, rather than the PatternFly Masthead. */
6
+ children?: React.ReactNode;
7
+ /** Additional classes added to the page header */
8
+ className?: string;
9
+ /** Sets the base component to render. Defaults to header */
10
+ component?: keyof React.JSX.IntrinsicElements;
11
+ }
12
+
13
+ export const PageHeader: React.FunctionComponent<PageHeaderProps> = ({
14
+ className,
15
+ children,
16
+ component = 'header',
17
+ ...props
18
+ }: PageHeaderProps) => {
19
+ const Component = component as any;
20
+
21
+ return (
22
+ <Component {...props} className={css(styles.pageHeader, className)}>
23
+ {children}
24
+ </Component>
25
+ );
26
+ };
27
+
28
+ PageHeader.displayName = 'PageHeader';
@@ -9,6 +9,7 @@ import { Nav, NavList, NavItem } from '../../Nav';
9
9
  import { SkipToContent } from '../../SkipToContent';
10
10
  import { PageBreadcrumb } from '../PageBreadcrumb';
11
11
  import { PageGroup } from '../PageGroup';
12
+ import { PageHeader } from '../PageHeader';
12
13
  import { Masthead } from '../../Masthead';
13
14
 
14
15
  import styles from '@patternfly/react-styles/css/components/Page/page';
@@ -487,4 +488,16 @@ describe('Page docked variant', () => {
487
488
  const pageDockMain = screen.getByText('Dock content').closest(`.${styles.pageDockMain}`);
488
489
  expect(pageDockMain).toBeInTheDocument();
489
490
  });
491
+
492
+ test('Renders PageHeader when passed to the masthead prop', () => {
493
+ render(
494
+ <Page {...props} masthead={<PageHeader>Custom header</PageHeader>}>
495
+ <PageSection>Custom content</PageSection>
496
+ </Page>
497
+ );
498
+
499
+ const header = screen.getByText('Custom header');
500
+ expect(header).toHaveClass(styles.pageHeader);
501
+ expect(header.parentElement).toHaveClass(styles.page);
502
+ });
490
503
  });
@@ -0,0 +1,33 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import styles from '@patternfly/react-styles/css/components/Page/page';
3
+ import { PageHeader } from '../PageHeader';
4
+
5
+ test('Renders children', () => {
6
+ render(<PageHeader>Header content</PageHeader>);
7
+ expect(screen.getByText('Header content')).toBeVisible();
8
+ });
9
+
10
+ test(`Renders with class ${styles.pageHeader} by default`, () => {
11
+ render(<PageHeader>Header content</PageHeader>);
12
+ expect(screen.getByText('Header content')).toHaveClass(styles.pageHeader, { exact: true });
13
+ });
14
+
15
+ test('Renders as a div by default', () => {
16
+ render(<PageHeader>Header content</PageHeader>);
17
+ expect(screen.getByText('Header content').tagName).toBe('HEADER');
18
+ });
19
+
20
+ test('Renders as a custom component when component is passed', () => {
21
+ render(<PageHeader component="div">Header content</PageHeader>);
22
+ expect(screen.getByText('Header content').tagName).toBe('DIV');
23
+ });
24
+
25
+ test('Renders with custom classes when className is passed', () => {
26
+ render(<PageHeader className="custom-class">Header content</PageHeader>);
27
+ expect(screen.getByText('Header content')).toHaveClass('custom-class');
28
+ });
29
+
30
+ test('Renders with spread props', () => {
31
+ render(<PageHeader id="custom-id">Header content</PageHeader>);
32
+ expect(screen.getByText('Header content')).toHaveAttribute('id', 'custom-id');
33
+ });
@@ -3,7 +3,16 @@ id: Page
3
3
  section: components
4
4
  cssPrefix: pf-v6-c-page
5
5
  propComponents:
6
- ['Page', 'PageSidebar', 'PageSidebarBody', 'PageSection', 'PageGroup', 'PageBreadcrumb', 'PageToggleButton']
6
+ [
7
+ 'Page',
8
+ 'PageHeader',
9
+ 'PageSidebar',
10
+ 'PageSidebarBody',
11
+ 'PageSection',
12
+ 'PageGroup',
13
+ 'PageBreadcrumb',
14
+ 'PageToggleButton'
15
+ ]
7
16
  ---
8
17
 
9
18
  import { useState, useLayoutEffect, useRef } from 'react';
@@ -16,7 +25,7 @@ import pageSectionWidthLimitMaxWidth from '@patternfly/react-tokens/dist/esm/c_p
16
25
 
17
26
  A page will typically contain the following components:
18
27
 
19
- - A `<Page>` with a `masthead` prop that often contains a [masthead](/components/masthead) component
28
+ - A `<Page>` with a `masthead` prop that often contains a [masthead](/components/masthead) or a `<PageHeader>`
20
29
 
21
30
  The `<MastheadMain>` component includes the smaller area that typically contains the `<MastheadToggle>` and a `<MastheadLogo>`. `<MastheadContent>` represents the main portion of the masthead, and will typically contain a `<Toolbar>` or other menu-like components, like a `<Dropdown>`.
22
31
 
@@ -24,6 +33,14 @@ The `<MastheadMain>` component includes the smaller area that typically contains
24
33
  - 1 or more `<PageSidebarBody>` components inside `<PageSidebar>` for vertical navigation or other sidebar content
25
34
  - 1 or more `<PageSection>` components
26
35
 
36
+ ### Page header
37
+
38
+ To use a page header instead of passing a [masthead](/components/masthead) directly, pass a `<PageHeader>` to the `masthead` property. `<PageHeader>` should only be used to wrap custom header content.
39
+
40
+ ```ts file="./PageHeaderContent.tsx"
41
+
42
+ ```
43
+
27
44
  ### Vertical navigation
28
45
 
29
46
  To add a vertical sidebar to a `<Page>`, pass a `<PageSidebar>` component into the `sidebar` property. To render navigation in the sidebar, pass a `<PageSidebarBody>` component to `<PageSidebar>`.
@@ -0,0 +1,19 @@
1
+ import { Page, PageHeader, PageSection } from '@patternfly/react-core';
2
+
3
+ export const PageHeaderContent: React.FunctionComponent = () => {
4
+ const pageHeader = <PageHeader>Page header</PageHeader>;
5
+
6
+ return (
7
+ <Page masthead={pageHeader}>
8
+ <PageSection aria-labelledby="header-example-section-1">
9
+ <h2 id="header-example-section-1">Page header example section 1</h2>
10
+ </PageSection>
11
+ <PageSection variant="secondary" aria-labelledby="header-example-section-2">
12
+ <h2 id="header-example-section-2">Page header example section 2 with secondary variant styling</h2>
13
+ </PageSection>
14
+ <PageSection aria-labelledby="header-example-section-3">
15
+ <h2 id="header-example-section-3">Page header example section 3</h2>
16
+ </PageSection>
17
+ </Page>
18
+ );
19
+ };
@@ -2,6 +2,7 @@ export * from './Page';
2
2
  export * from './PageBody';
3
3
  export * from './PageBreadcrumb';
4
4
  export * from './PageGroup';
5
+ export * from './PageHeader';
5
6
  export * from './PageSidebar';
6
7
  export * from './PageSidebarBody';
7
8
  export * from './PageSection';
@@ -2,9 +2,7 @@
2
2
 
3
3
  exports[`SearchInput advanced search 1`] = `
4
4
  <DocumentFragment>
5
- <div
6
- class=""
7
- >
5
+ <div>
8
6
  <div
9
7
  class="pf-v6-c-input-group"
10
8
  >
@@ -145,7 +143,6 @@ exports[`SearchInput advanced search 1`] = `
145
143
  exports[`SearchInput advanced search with custom attributes 1`] = `
146
144
  <DocumentFragment>
147
145
  <div
148
- class=""
149
146
  data-testid="test-id"
150
147
  >
151
148
  <div
@@ -282,7 +279,6 @@ exports[`SearchInput advanced search with custom attributes 1`] = `
282
279
  </div>
283
280
  </div>
284
281
  <div
285
- class=""
286
282
  data-popper-escaped="true"
287
283
  data-popper-placement="bottom-start"
288
284
  data-popper-reference-hidden="true"
@@ -490,9 +486,7 @@ exports[`SearchInput advanced search with custom attributes 1`] = `
490
486
 
491
487
  exports[`SearchInput renders search input in strict mode 1`] = `
492
488
  <DocumentFragment>
493
- <div
494
- class=""
495
- >
489
+ <div>
496
490
  <div
497
491
  class="pf-v6-c-input-group"
498
492
  >
@@ -2,9 +2,7 @@
2
2
 
3
3
  exports[`empty 1`] = `
4
4
  <DocumentFragment>
5
- <span
6
- class=""
7
- />
5
+ <span />
8
6
  </DocumentFragment>
9
7
  `;
10
8
 
@@ -8,9 +8,7 @@ exports[`Flex Nested flex 1`] = `
8
8
  <div
9
9
  class="pf-v6-l-flex"
10
10
  >
11
- <div
12
- class=""
13
- >
11
+ <div>
14
12
  Test
15
13
  </div>
16
14
  </div>
@@ -23,9 +21,7 @@ exports[`Flex Simple flex with single item 1`] = `
23
21
  <div
24
22
  class="pf-v6-l-flex"
25
23
  >
26
- <div
27
- class=""
28
- >
24
+ <div>
29
25
  Test
30
26
  </div>
31
27
  </div>
@@ -37,9 +33,7 @@ exports[`Flex alternative component 1`] = `
37
33
  <ul
38
34
  class="pf-v6-l-flex"
39
35
  >
40
- <li
41
- class=""
42
- >
36
+ <li>
43
37
  Test
44
38
  </li>
45
39
  </ul>