@patternfly/react-core 5.1.1-prerelease.2 → 5.1.1-prerelease.4

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 (138) hide show
  1. package/CHANGELOG.md +10 -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/Chip/package.json +1 -1
  21. package/dist/dynamic/components/ClipboardCopy/package.json +1 -1
  22. package/dist/dynamic/components/CodeBlock/package.json +1 -1
  23. package/dist/dynamic/components/DataList/package.json +1 -1
  24. package/dist/dynamic/components/DatePicker/package.json +1 -1
  25. package/dist/dynamic/components/DescriptionList/package.json +1 -1
  26. package/dist/dynamic/components/Divider/package.json +1 -1
  27. package/dist/dynamic/components/DragDrop/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/Hint/package.json +1 -1
  38. package/dist/dynamic/components/Icon/package.json +1 -1
  39. package/dist/dynamic/components/InputGroup/package.json +1 -1
  40. package/dist/dynamic/components/JumpLinks/package.json +1 -1
  41. package/dist/dynamic/components/Label/package.json +1 -1
  42. package/dist/dynamic/components/List/package.json +1 -1
  43. package/dist/dynamic/components/LoginPage/package.json +1 -1
  44. package/dist/dynamic/components/Masthead/package.json +1 -1
  45. package/dist/dynamic/components/Menu/package.json +1 -1
  46. package/dist/dynamic/components/MenuToggle/package.json +1 -1
  47. package/dist/dynamic/components/Modal/package.json +1 -1
  48. package/dist/dynamic/components/MultipleFileUpload/package.json +1 -1
  49. package/dist/dynamic/components/Nav/package.json +1 -1
  50. package/dist/dynamic/components/NotificationBadge/package.json +1 -1
  51. package/dist/dynamic/components/NotificationDrawer/package.json +1 -1
  52. package/dist/dynamic/components/NumberInput/package.json +1 -1
  53. package/dist/dynamic/components/OverflowMenu/package.json +1 -1
  54. package/dist/dynamic/components/Page/package.json +1 -1
  55. package/dist/dynamic/components/Pagination/package.json +1 -1
  56. package/dist/dynamic/components/Panel/package.json +1 -1
  57. package/dist/dynamic/components/Popover/package.json +1 -1
  58. package/dist/dynamic/components/Progress/package.json +1 -1
  59. package/dist/dynamic/components/ProgressStepper/package.json +1 -1
  60. package/dist/dynamic/components/Radio/package.json +1 -1
  61. package/dist/dynamic/components/SearchInput/package.json +1 -1
  62. package/dist/dynamic/components/Select/package.json +1 -1
  63. package/dist/dynamic/components/Sidebar/package.json +1 -1
  64. package/dist/dynamic/components/SimpleList/package.json +1 -1
  65. package/dist/dynamic/components/Skeleton/package.json +1 -1
  66. package/dist/dynamic/components/SkipToContent/package.json +1 -1
  67. package/dist/dynamic/components/Slider/package.json +1 -1
  68. package/dist/dynamic/components/Spinner/package.json +1 -1
  69. package/dist/dynamic/components/Switch/package.json +1 -1
  70. package/dist/dynamic/components/Tabs/package.json +1 -1
  71. package/dist/dynamic/components/Text/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/Tile/package.json +1 -1
  76. package/dist/dynamic/components/TimePicker/package.json +1 -1
  77. package/dist/dynamic/components/Timestamp/package.json +1 -1
  78. package/dist/dynamic/components/Title/package.json +1 -1
  79. package/dist/dynamic/components/ToggleGroup/package.json +1 -1
  80. package/dist/dynamic/components/Toolbar/package.json +1 -1
  81. package/dist/dynamic/components/Tooltip/package.json +1 -1
  82. package/dist/dynamic/components/TreeView/package.json +1 -1
  83. package/dist/dynamic/components/Truncate/package.json +1 -1
  84. package/dist/dynamic/components/Wizard/hooks/package.json +1 -1
  85. package/dist/dynamic/components/Wizard/package.json +1 -1
  86. package/dist/dynamic/deprecated/components/ApplicationLauncher/package.json +1 -1
  87. package/dist/dynamic/deprecated/components/ContextSelector/package.json +1 -1
  88. package/dist/dynamic/deprecated/components/Dropdown/package.json +1 -1
  89. package/dist/dynamic/deprecated/components/OptionsMenu/package.json +1 -1
  90. package/dist/dynamic/deprecated/components/PageHeader/package.json +1 -1
  91. package/dist/dynamic/deprecated/components/Select/package.json +1 -1
  92. package/dist/dynamic/deprecated/components/Wizard/package.json +1 -1
  93. package/dist/dynamic/deprecated/components/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/constants/package.json +1 -1
  100. package/dist/dynamic/helpers/datetimeUtils/package.json +1 -1
  101. package/dist/dynamic/helpers/favorites/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/package.json +1 -1
  105. package/dist/dynamic/helpers/resizeObserver/package.json +1 -1
  106. package/dist/dynamic/helpers/typeUtils/package.json +1 -1
  107. package/dist/dynamic/helpers/useInterval/package.json +1 -1
  108. package/dist/dynamic/helpers/useIsomorphicLayout/package.json +1 -1
  109. package/dist/dynamic/helpers/useUnmountEffect/package.json +1 -1
  110. package/dist/dynamic/helpers/util/package.json +1 -1
  111. package/dist/dynamic/layouts/Bullseye/package.json +1 -1
  112. package/dist/dynamic/layouts/Flex/package.json +1 -1
  113. package/dist/dynamic/layouts/Gallery/package.json +1 -1
  114. package/dist/dynamic/layouts/Grid/package.json +1 -1
  115. package/dist/dynamic/layouts/Level/package.json +1 -1
  116. package/dist/dynamic/layouts/Split/package.json +1 -1
  117. package/dist/dynamic/layouts/Stack/package.json +1 -1
  118. package/dist/dynamic/next/components/package.json +1 -1
  119. package/dist/esm/components/Drawer/DrawerPanelContent.d.ts +16 -0
  120. package/dist/esm/components/Drawer/DrawerPanelContent.d.ts.map +1 -1
  121. package/dist/esm/components/Drawer/DrawerPanelContent.js +55 -15
  122. package/dist/esm/components/Drawer/DrawerPanelContent.js.map +1 -1
  123. package/dist/js/components/Drawer/DrawerPanelContent.d.ts +16 -0
  124. package/dist/js/components/Drawer/DrawerPanelContent.d.ts.map +1 -1
  125. package/dist/js/components/Drawer/DrawerPanelContent.js +55 -15
  126. package/dist/js/components/Drawer/DrawerPanelContent.js.map +1 -1
  127. package/dist/umd/react-core.min.js +1 -1
  128. package/helpers/package.json +1 -1
  129. package/layouts/package.json +1 -1
  130. package/next/package.json +1 -1
  131. package/package.json +2 -2
  132. package/src/components/Drawer/DrawerPanelContent.tsx +120 -55
  133. package/src/components/Drawer/__tests__/DrawerPanelContent.test.tsx +89 -0
  134. package/src/components/Drawer/examples/Drawer.md +31 -0
  135. package/src/components/Drawer/examples/DrawerFocusTrap.tsx +58 -0
  136. package/src/demos/NotificationDrawer/NotificationDrawer.md +2 -1435
  137. package/src/demos/NotificationDrawer/examples/NotificationDrawerBasic.tsx +557 -0
  138. package/src/demos/NotificationDrawer/examples/NotificationDrawerGrouped.tsx +796 -0
@@ -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":"5.1.1-prerelease.1","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":"5.1.1-prerelease.3","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":"5.1.1-prerelease.1","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":"5.1.1-prerelease.3","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":"5.1.1-prerelease.1","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":"5.1.1-prerelease.3","private":true}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "5.1.1-prerelease.2",
3
+ "version": "5.1.1-prerelease.4",
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",
@@ -70,5 +70,5 @@
70
70
  "react": "^17 || ^18",
71
71
  "react-dom": "^17 || ^18"
72
72
  },
73
- "gitHead": "52cbf11e7e407d70d674a160da7f432d197c5f57"
73
+ "gitHead": "12ac5fc7a5280fd5f3fc1ad9bb6134ca028f2ed9"
74
74
  }
@@ -4,6 +4,22 @@ import { css } from '@patternfly/react-styles';
4
4
  import { DrawerColorVariant, DrawerContext } from './Drawer';
5
5
  import { formatBreakpointMods } from '../../helpers/util';
6
6
  import { GenerateId } from '../../helpers/GenerateId/GenerateId';
7
+ import { FocusTrap } from '../../helpers/FocusTrap/FocusTrap';
8
+
9
+ export interface DrawerPanelFocusTrapObject {
10
+ /** Enables a focus trap on the drawer panel content. This will also automatically
11
+ * handle focus management when the panel expands and when it collapses. Do not pass
12
+ * this prop if the isStatic prop on the drawer component is true.
13
+ */
14
+ enabled?: boolean;
15
+ /** The element to focus when the drawer panel content expands. By default the
16
+ * first focusable element will receive focus. If there are no focusable elements, the
17
+ * panel itself will receive focus.
18
+ */
19
+ elementToFocusOnExpand?: HTMLElement | SVGElement | string;
20
+ /** One or more id's to use for the drawer panel content's accessible label. */
21
+ 'aria-labelledby'?: string;
22
+ }
7
23
 
8
24
  export interface DrawerPanelContentProps extends React.HTMLProps<HTMLDivElement> {
9
25
  /** Additional classes added to the drawer. */
@@ -37,6 +53,8 @@ export interface DrawerPanelContentProps extends React.HTMLProps<HTMLDivElement>
37
53
  };
38
54
  /** Color variant of the background of the drawer panel */
39
55
  colorVariant?: DrawerColorVariant | 'light-200' | 'no-background' | 'default';
56
+ /** Adds and customizes a focus trap on the drawer panel content. */
57
+ focusTrap?: DrawerPanelFocusTrapObject;
40
58
  }
41
59
  let isResizing: boolean = null;
42
60
  let newSize: number = 0;
@@ -55,6 +73,7 @@ export const DrawerPanelContent: React.FunctionComponent<DrawerPanelContentProps
55
73
  resizeAriaLabel = 'Resize',
56
74
  widths,
57
75
  colorVariant = DrawerColorVariant.default,
76
+ focusTrap,
58
77
  ...props
59
78
  }: DrawerPanelContentProps) => {
60
79
  const panel = React.useRef<HTMLDivElement>();
@@ -64,6 +83,8 @@ export const DrawerPanelContent: React.FunctionComponent<DrawerPanelContentProps
64
83
  React.useContext(DrawerContext);
65
84
  const hidden = isStatic ? false : !isExpanded;
66
85
  const [isExpandedInternal, setIsExpandedInternal] = React.useState(!hidden);
86
+ const [isFocusTrapActive, setIsFocusTrapActive] = React.useState(false);
87
+ const previouslyFocusedElement = React.useRef(null);
67
88
  let currWidth: number = 0;
68
89
  let panelRect: DOMRect;
69
90
  let right: number;
@@ -71,6 +92,13 @@ export const DrawerPanelContent: React.FunctionComponent<DrawerPanelContentProps
71
92
  let bottom: number;
72
93
  let setInitialVals: boolean = true;
73
94
 
95
+ if (isStatic && focusTrap?.enabled) {
96
+ // eslint-disable-next-line no-console
97
+ console.warn(
98
+ `DrawerPanelContent: The focusTrap.enabled prop cannot be true if the Drawer's isStatic prop is true. This will cause a permanent focus trap.`
99
+ );
100
+ }
101
+
74
102
  React.useEffect(() => {
75
103
  if (!isStatic && isExpanded) {
76
104
  setIsExpandedInternal(isExpanded);
@@ -246,64 +274,101 @@ export const DrawerPanelContent: React.FunctionComponent<DrawerPanelContentProps
246
274
  if (maxSize) {
247
275
  boundaryCssVars['--pf-v5-c-drawer__panel--md--FlexBasis--max'] = maxSize;
248
276
  }
277
+
278
+ const isValidFocusTrap = focusTrap?.enabled && !isStatic;
279
+ const Component = isValidFocusTrap ? FocusTrap : 'div';
280
+
249
281
  return (
250
282
  <GenerateId prefix="pf-drawer-panel-">
251
- {(panelId) => (
252
- <div
253
- id={id || panelId}
254
- className={css(
255
- styles.drawerPanel,
256
- isResizable && styles.modifiers.resizable,
257
- hasNoBorder && styles.modifiers.noBorder,
258
- formatBreakpointMods(widths, styles),
259
- colorVariant === DrawerColorVariant.light200 && styles.modifiers.light_200,
260
- colorVariant === DrawerColorVariant.noBackground && styles.modifiers.noBackground,
261
- className
262
- )}
263
- ref={panel}
264
- onTransitionEnd={(ev) => {
265
- if ((ev.target as HTMLElement) === panel.current) {
266
- if (!hidden && ev.nativeEvent.propertyName === 'transform') {
267
- onExpand(ev);
283
+ {(panelId) => {
284
+ const focusTrapProps = {
285
+ tabIndex: -1,
286
+ 'aria-modal': true,
287
+ role: 'dialog',
288
+ active: isFocusTrapActive,
289
+ 'aria-labelledby': focusTrap?.['aria-labelledby'] || id || panelId,
290
+ focusTrapOptions: {
291
+ fallbackFocus: () => panel.current,
292
+ onActivate: () => {
293
+ if (previouslyFocusedElement.current !== document.activeElement) {
294
+ previouslyFocusedElement.current = document.activeElement;
295
+ }
296
+ },
297
+ onDeactivate: () => {
298
+ previouslyFocusedElement.current &&
299
+ previouslyFocusedElement.current.focus &&
300
+ previouslyFocusedElement.current.focus();
301
+ },
302
+ clickOutsideDeactivates: true,
303
+ returnFocusOnDeactivate: false,
304
+ // FocusTrap's initialFocus can accept false as a value to prevent initial focus.
305
+ // We want to prevent this in case false is ever passed in.
306
+ initialFocus: focusTrap?.elementToFocusOnExpand || undefined,
307
+ escapeDeactivates: false
308
+ }
309
+ };
310
+
311
+ return (
312
+ <Component
313
+ {...(isValidFocusTrap && focusTrapProps)}
314
+ id={id || panelId}
315
+ className={css(
316
+ styles.drawerPanel,
317
+ isResizable && styles.modifiers.resizable,
318
+ hasNoBorder && styles.modifiers.noBorder,
319
+ formatBreakpointMods(widths, styles),
320
+ colorVariant === DrawerColorVariant.light200 && styles.modifiers.light_200,
321
+ colorVariant === DrawerColorVariant.noBackground && styles.modifiers.noBackground,
322
+ className
323
+ )}
324
+ onTransitionEnd={(ev) => {
325
+ if ((ev.target as HTMLElement) === panel.current) {
326
+ if (!hidden && ev.nativeEvent.propertyName === 'transform') {
327
+ onExpand(ev);
328
+ }
329
+ setIsExpandedInternal(!hidden);
330
+ if (isValidFocusTrap && ev.nativeEvent.propertyName === 'transform') {
331
+ setIsFocusTrapActive((prevIsFocusTrapActive) => !prevIsFocusTrapActive);
332
+ }
268
333
  }
269
- setIsExpandedInternal(!hidden);
270
- }
271
- }}
272
- hidden={hidden}
273
- {...((defaultSize || minSize || maxSize) && {
274
- style: boundaryCssVars as React.CSSProperties
275
- })}
276
- {...props}
277
- >
278
- {isExpandedInternal && (
279
- <React.Fragment>
280
- {isResizable && (
281
- <React.Fragment>
282
- <div
283
- className={css(styles.drawerSplitter, position !== 'bottom' && styles.modifiers.vertical)}
284
- role="separator"
285
- tabIndex={0}
286
- aria-orientation={position === 'bottom' ? 'horizontal' : 'vertical'}
287
- aria-label={resizeAriaLabel}
288
- aria-valuenow={separatorValue}
289
- aria-valuemin={0}
290
- aria-valuemax={100}
291
- aria-controls={id || panelId}
292
- onMouseDown={handleMousedown}
293
- onKeyDown={handleKeys}
294
- onTouchStart={handleTouchStart}
295
- ref={splitterRef}
296
- >
297
- <div className={css(styles.drawerSplitterHandle)} aria-hidden></div>
298
- </div>
299
- <div className={css(styles.drawerPanelMain)}>{children}</div>
300
- </React.Fragment>
301
- )}
302
- {!isResizable && children}
303
- </React.Fragment>
304
- )}
305
- </div>
306
- )}
334
+ }}
335
+ hidden={hidden}
336
+ {...((defaultSize || minSize || maxSize) && {
337
+ style: boundaryCssVars as React.CSSProperties
338
+ })}
339
+ {...props}
340
+ ref={panel}
341
+ >
342
+ {isExpandedInternal && (
343
+ <React.Fragment>
344
+ {isResizable && (
345
+ <React.Fragment>
346
+ <div
347
+ className={css(styles.drawerSplitter, position !== 'bottom' && styles.modifiers.vertical)}
348
+ role="separator"
349
+ tabIndex={0}
350
+ aria-orientation={position === 'bottom' ? 'horizontal' : 'vertical'}
351
+ aria-label={resizeAriaLabel}
352
+ aria-valuenow={separatorValue}
353
+ aria-valuemin={0}
354
+ aria-valuemax={100}
355
+ aria-controls={id || panelId}
356
+ onMouseDown={handleMousedown}
357
+ onKeyDown={handleKeys}
358
+ onTouchStart={handleTouchStart}
359
+ ref={splitterRef}
360
+ >
361
+ <div className={css(styles.drawerSplitterHandle)} aria-hidden></div>
362
+ </div>
363
+ <div className={css(styles.drawerPanelMain)}>{children}</div>
364
+ </React.Fragment>
365
+ )}
366
+ {!isResizable && children}
367
+ </React.Fragment>
368
+ )}
369
+ </Component>
370
+ );
371
+ }}
307
372
  </GenerateId>
308
373
  );
309
374
  };
@@ -0,0 +1,89 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { DrawerPanelContent } from '../DrawerPanelContent';
4
+ import { Drawer } from '../Drawer';
5
+
6
+ test('Does not render with aria-labelledby by default', () => {
7
+ render(
8
+ <Drawer isExpanded>
9
+ <DrawerPanelContent>Drawer panel content</DrawerPanelContent>
10
+ </Drawer>
11
+ );
12
+
13
+ expect(screen.getByText('Drawer panel content')).not.toHaveAccessibleName();
14
+ });
15
+
16
+ test('Renders with aria-labelledby when focusTrap.enabled is true', () => {
17
+ render(
18
+ <Drawer isExpanded>
19
+ <DrawerPanelContent focusTrap={{ enabled: true }}>Drawer panel content</DrawerPanelContent>
20
+ </Drawer>
21
+ );
22
+
23
+ expect(screen.getByText('Drawer panel content')).toHaveAccessibleName('Drawer panel content');
24
+ });
25
+
26
+ test('Renders with aria-labelledby when id is passed in', () => {
27
+ render(
28
+ <Drawer isExpanded>
29
+ <DrawerPanelContent id="drawer-panel-content" focusTrap={{ enabled: true }}>
30
+ Drawer panel content
31
+ </DrawerPanelContent>
32
+ </Drawer>
33
+ );
34
+
35
+ expect(screen.getByText('Drawer panel content')).toHaveAccessibleName('Drawer panel content');
36
+ });
37
+
38
+ test('Renders with custom aria-labelledby', () => {
39
+ render(
40
+ <Drawer isExpanded>
41
+ <DrawerPanelContent focusTrap={{ enabled: true, 'aria-labelledby': 'drawer-panel-title' }}>
42
+ <span id="drawer-panel-title">Title</span>
43
+ <span>Drawer panel content</span>
44
+ </DrawerPanelContent>
45
+ </Drawer>
46
+ );
47
+
48
+ expect(screen.getByText('Drawer panel content').parentElement).toHaveAccessibleName('Title');
49
+ });
50
+
51
+ test('Does not render with aria-modal="true" by default', () => {
52
+ render(
53
+ <Drawer isExpanded>
54
+ <DrawerPanelContent>Drawer panel content</DrawerPanelContent>
55
+ </Drawer>
56
+ );
57
+
58
+ expect(screen.getByText('Drawer panel content')).not.toHaveAttribute('aria-modal');
59
+ });
60
+
61
+ test('Renders with aria-modal="true" when focusTrap.enabled is true', () => {
62
+ render(
63
+ <Drawer isExpanded>
64
+ <DrawerPanelContent focusTrap={{ enabled: true }}>Drawer panel content</DrawerPanelContent>
65
+ </Drawer>
66
+ );
67
+
68
+ expect(screen.getByText('Drawer panel content')).toHaveAttribute('aria-modal', 'true');
69
+ });
70
+
71
+ test('Does not render with role="dialog" by default', () => {
72
+ render(
73
+ <Drawer isExpanded>
74
+ <DrawerPanelContent>Drawer panel content</DrawerPanelContent>
75
+ </Drawer>
76
+ );
77
+
78
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
79
+ });
80
+
81
+ test('Renders with role="dialog" when focusTrap.enabled is true', () => {
82
+ render(
83
+ <Drawer isExpanded>
84
+ <DrawerPanelContent focusTrap={{ enabled: true }}>Drawer panel content</DrawerPanelContent>
85
+ </Drawer>
86
+ );
87
+
88
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
89
+ });
@@ -13,6 +13,7 @@ propComponents:
13
13
  DrawerActions,
14
14
  DrawerCloseButton,
15
15
  DrawerColorVariant,
16
+ DrawerPanelFocusTrapObject
16
17
  ]
17
18
  section: components
18
19
  ---
@@ -22,56 +23,67 @@ section: components
22
23
  ### Basic
23
24
 
24
25
  ```ts file="./DrawerBasic.tsx"
26
+
25
27
  ```
26
28
 
27
29
  ### Panel on right
28
30
 
29
31
  ```ts file="./DrawerPanelRight.tsx"
32
+
30
33
  ```
31
34
 
32
35
  ### Panel on left
33
36
 
34
37
  ```ts file="./DrawerPanelLeft.tsx"
38
+
35
39
  ```
36
40
 
37
41
  ### Panel on bottom
38
42
 
39
43
  ```ts file="./DrawerPanelBottom.tsx"
44
+
40
45
  ```
41
46
 
42
47
  ### Basic inline
43
48
 
44
49
  ```ts file="./DrawerBasicInline.tsx"
50
+
45
51
  ```
46
52
 
47
53
  ### Inline panel on right
48
54
 
49
55
  ```ts file="./DrawerInlinePanelRight.tsx"
56
+
50
57
  ```
51
58
 
52
59
  ### Inline panel on left
53
60
 
54
61
  ```ts file="./DrawerInlinePanelLeft.tsx"
62
+
55
63
  ```
56
64
 
57
65
  ### Stacked content body elements
58
66
 
59
67
  ```ts file="./DrawerStackedContentBodyElements.tsx"
68
+
60
69
  ```
61
70
 
62
71
  ### Modified content padding
63
72
 
64
73
  ```ts file="DrawerModifiedContentPadding.tsx"
74
+
65
75
  ```
66
76
 
67
77
  ### Modified panel padding
68
78
 
69
79
  ```ts file="DrawerModifiedPanelPadding.tsx"
80
+
70
81
  ```
71
82
 
72
83
  ### Additional section above drawer content
73
84
 
74
85
  ```ts file="DrawerAdditionalSectionAboveContent.tsx"
86
+
75
87
  ```
76
88
 
77
89
  ### Static drawer
@@ -79,34 +91,53 @@ section: components
79
91
  **Note:** For mobile viewports, all drawer variants behave the same way. At the `md` breakpoint, or where `.pf-m-static{-on-[lg, xl, 2xl]}` is applied, the `static drawer` variant’s `close button` is automatically hidden because the drawer panel doesn’t close by design.
80
92
 
81
93
  ```ts file="DrawerStatic.tsx"
94
+
82
95
  ```
83
96
 
84
97
  ### Breakpoint
85
98
 
86
99
  ```ts file="DrawerBreakpoint.tsx"
100
+
87
101
  ```
88
102
 
89
103
  ### Resizable on right
90
104
 
91
105
  ```ts file="DrawerResizableOnRight.tsx"
106
+
92
107
  ```
93
108
 
94
109
  ### Resizable on left
95
110
 
96
111
  ```ts file="DrawerResizableOnLeft.tsx"
112
+
97
113
  ```
98
114
 
99
115
  ### Resizable on bottom
100
116
 
101
117
  ```ts file="DrawerResizableOnBottom.tsx"
118
+
102
119
  ```
103
120
 
104
121
  ### Resizable on inline
105
122
 
106
123
  ```ts file="DrawerResizableOnInline.tsx"
124
+
107
125
  ```
108
126
 
109
127
  ### Panel with secondary background
110
128
 
111
129
  ```ts file="DrawerSecondaryBackground.tsx"
130
+
131
+ ```
132
+
133
+ ### With focus trap
134
+
135
+ When a [focus trap](/accessibility/product-development-guide#trapping-focus) is enabled on an element, a user will only be able to interact with the contents of that element until the focus trap is closed or deactivated.
136
+
137
+ To enable and customize a focus trap on a drawer panel, apply the `focusTrap` property to the `<DrawerPanelContent>` component. Enabling a focus trap with `focusTrap.enabled` will also automatically place focus on the first focusable element when the drawer panel expands, and return focus to the previously focused element when it collapses.
138
+
139
+ To customize which element receives focus when the drawer panel expands, use the `focusTrap.elementToFocusOnExpand` property.
140
+
141
+ ```ts file="./DrawerFocusTrap.tsx"
142
+
112
143
  ```
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import {
3
+ Drawer,
4
+ DrawerPanelContent,
5
+ DrawerContent,
6
+ DrawerContentBody,
7
+ DrawerHead,
8
+ DrawerActions,
9
+ DrawerCloseButton,
10
+ Button
11
+ } from '@patternfly/react-core';
12
+
13
+ export const DrawerFocusTrap: React.FunctionComponent = () => {
14
+ const [isExpanded, setIsExpanded] = React.useState(false);
15
+
16
+ const onClick = () => {
17
+ setIsExpanded(!isExpanded);
18
+ };
19
+
20
+ const onCloseClick = () => {
21
+ setIsExpanded(false);
22
+ };
23
+
24
+ // An onKeyDown property must be passed to the Drawer component to handle closing
25
+ // the drawer panel and deactivating the focus trap via the Escape key.
26
+ const onEscape = (event: React.KeyboardEvent) => {
27
+ if (event.key === 'Escape') {
28
+ setIsExpanded(false);
29
+ }
30
+ };
31
+
32
+ const panelContent = (
33
+ <DrawerPanelContent focusTrap={{ enabled: true }}>
34
+ <DrawerHead>
35
+ <span>drawer-panel</span>
36
+ <DrawerActions>
37
+ <DrawerCloseButton onClick={onCloseClick} />
38
+ </DrawerActions>
39
+ </DrawerHead>
40
+ </DrawerPanelContent>
41
+ );
42
+
43
+ const drawerContent =
44
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus pretium est a porttitor vehicula. Quisque vel commodo urna. Morbi mattis rutrum ante, id vehicula ex accumsan ut. Morbi viverra, eros vel porttitor facilisis, eros purus aliquet erat,nec lobortis felis elit pulvinar sem. Vivamus vulputate, risus eget commodo eleifend, eros nibh porta quam, vitae lacinia leo libero at magna. Maecenas aliquam sagittis orci, et posuere nisi ultrices sit amet. Aliquam ex odio, malesuada sed posuere quis, pellentesque at mauris. Phasellus venenatis massa ex, eget pulvinar libero auctor pretium. Aliquam erat volutpat. Duis euismod justo in quam ullamcorper, in commodo massa vulputate.';
45
+
46
+ return (
47
+ <React.Fragment>
48
+ <Button aria-expanded={isExpanded} onClick={onClick}>
49
+ Toggle drawer with focus trap
50
+ </Button>
51
+ <Drawer onKeyDown={onEscape} isExpanded={isExpanded}>
52
+ <DrawerContent panelContent={panelContent}>
53
+ <DrawerContentBody>{drawerContent}</DrawerContentBody>
54
+ </DrawerContent>
55
+ </Drawer>
56
+ </React.Fragment>
57
+ );
58
+ };