@patternfly/react-core 6.5.0-prerelease.3 → 6.5.0-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 (166) 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/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/TextArea/package.json +1 -1
  72. package/dist/dynamic/components/TextInput/package.json +1 -1
  73. package/dist/dynamic/components/TextInputGroup/package.json +1 -1
  74. package/dist/dynamic/components/TimePicker/package.json +1 -1
  75. package/dist/dynamic/components/Timestamp/package.json +1 -1
  76. package/dist/dynamic/components/Title/package.json +1 -1
  77. package/dist/dynamic/components/ToggleGroup/package.json +1 -1
  78. package/dist/dynamic/components/Toolbar/package.json +1 -1
  79. package/dist/dynamic/components/Tooltip/package.json +1 -1
  80. package/dist/dynamic/components/TreeView/package.json +1 -1
  81. package/dist/dynamic/components/Truncate/package.json +1 -1
  82. package/dist/dynamic/components/Wizard/hooks/package.json +1 -1
  83. package/dist/dynamic/components/Wizard/package.json +1 -1
  84. package/dist/dynamic/deprecated/components/Chip/package.json +1 -1
  85. package/dist/dynamic/deprecated/components/DragDrop/package.json +1 -1
  86. package/dist/dynamic/deprecated/components/DualListSelector/package.json +1 -1
  87. package/dist/dynamic/deprecated/components/Modal/package.json +1 -1
  88. package/dist/dynamic/deprecated/components/Tile/package.json +1 -1
  89. package/dist/dynamic/deprecated/components/Wizard/package.json +1 -1
  90. package/dist/dynamic/deprecated/components/package.json +1 -1
  91. package/dist/dynamic/helpers/AnimationsProvider/AnimationsProvider/package.json +1 -1
  92. package/dist/dynamic/helpers/AnimationsProvider/package.json +1 -1
  93. package/dist/dynamic/helpers/FocusTrap/FocusTrap/package.json +1 -1
  94. package/dist/dynamic/helpers/GenerateId/GenerateId/package.json +1 -1
  95. package/dist/dynamic/helpers/KeyboardHandler/package.json +1 -1
  96. package/dist/dynamic/helpers/OUIA/ouia/package.json +1 -1
  97. package/dist/dynamic/helpers/Popper/Popper/package.json +1 -1
  98. package/dist/dynamic/helpers/constants/package.json +1 -1
  99. package/dist/dynamic/helpers/datetimeUtils/package.json +1 -1
  100. package/dist/dynamic/helpers/fileUtils/package.json +1 -1
  101. package/dist/dynamic/helpers/htmlConstants/package.json +1 -1
  102. package/dist/dynamic/helpers/package.json +1 -1
  103. package/dist/dynamic/helpers/resizeObserver/package.json +1 -1
  104. package/dist/dynamic/helpers/typeUtils/package.json +1 -1
  105. package/dist/dynamic/helpers/useInterval/package.json +1 -1
  106. package/dist/dynamic/helpers/useIsomorphicLayout/package.json +1 -1
  107. package/dist/dynamic/helpers/useUnmountEffect/package.json +1 -1
  108. package/dist/dynamic/helpers/util/package.json +1 -1
  109. package/dist/dynamic/layouts/Bullseye/package.json +1 -1
  110. package/dist/dynamic/layouts/Flex/package.json +1 -1
  111. package/dist/dynamic/layouts/Gallery/package.json +1 -1
  112. package/dist/dynamic/layouts/Grid/package.json +1 -1
  113. package/dist/dynamic/layouts/Level/package.json +1 -1
  114. package/dist/dynamic/layouts/Split/package.json +1 -1
  115. package/dist/dynamic/layouts/Stack/package.json +1 -1
  116. package/dist/dynamic/styles/package.json +1 -1
  117. package/dist/esm/components/DataList/DataList.d.ts +2 -0
  118. package/dist/esm/components/DataList/DataList.d.ts.map +1 -1
  119. package/dist/esm/components/DataList/DataList.js +2 -2
  120. package/dist/esm/components/DataList/DataList.js.map +1 -1
  121. package/dist/esm/components/ExpandableSection/ExpandableSection.d.ts +5 -0
  122. package/dist/esm/components/ExpandableSection/ExpandableSection.d.ts.map +1 -1
  123. package/dist/esm/components/ExpandableSection/ExpandableSection.js +3 -2
  124. package/dist/esm/components/ExpandableSection/ExpandableSection.js.map +1 -1
  125. package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.d.ts +5 -0
  126. package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.d.ts.map +1 -1
  127. package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.js +3 -2
  128. package/dist/esm/components/ExpandableSection/ExpandableSectionToggle.js.map +1 -1
  129. package/dist/esm/components/Truncate/Truncate.d.ts.map +1 -1
  130. package/dist/esm/components/Truncate/Truncate.js +5 -4
  131. package/dist/esm/components/Truncate/Truncate.js.map +1 -1
  132. package/dist/js/components/DataList/DataList.d.ts +2 -0
  133. package/dist/js/components/DataList/DataList.d.ts.map +1 -1
  134. package/dist/js/components/DataList/DataList.js +2 -2
  135. package/dist/js/components/DataList/DataList.js.map +1 -1
  136. package/dist/js/components/ExpandableSection/ExpandableSection.d.ts +5 -0
  137. package/dist/js/components/ExpandableSection/ExpandableSection.d.ts.map +1 -1
  138. package/dist/js/components/ExpandableSection/ExpandableSection.js +3 -2
  139. package/dist/js/components/ExpandableSection/ExpandableSection.js.map +1 -1
  140. package/dist/js/components/ExpandableSection/ExpandableSectionToggle.d.ts +5 -0
  141. package/dist/js/components/ExpandableSection/ExpandableSectionToggle.d.ts.map +1 -1
  142. package/dist/js/components/ExpandableSection/ExpandableSectionToggle.js +3 -2
  143. package/dist/js/components/ExpandableSection/ExpandableSectionToggle.js.map +1 -1
  144. package/dist/js/components/Truncate/Truncate.d.ts.map +1 -1
  145. package/dist/js/components/Truncate/Truncate.js +5 -4
  146. package/dist/js/components/Truncate/Truncate.js.map +1 -1
  147. package/dist/umd/assets/{output-b6v4lKPy.css → output-m_8X2Duh.css} +21097 -21097
  148. package/dist/umd/react-core.min.js +1 -1
  149. package/helpers/package.json +1 -1
  150. package/layouts/package.json +1 -1
  151. package/next/package.json +1 -1
  152. package/package.json +2 -2
  153. package/src/components/ClipboardCopy/__tests__/ClipboardCopyTruncateIntegration.test.tsx +32 -0
  154. package/src/components/DataList/DataList.tsx +4 -0
  155. package/src/components/DataList/examples/DataList.md +5 -0
  156. package/src/components/DataList/examples/DataListPlain.tsx +32 -0
  157. package/src/components/ExpandableSection/ExpandableSection.tsx +9 -2
  158. package/src/components/ExpandableSection/ExpandableSectionToggle.tsx +48 -38
  159. package/src/components/ExpandableSection/__tests__/ExpandableSection.test.tsx +29 -0
  160. package/src/components/ExpandableSection/__tests__/ExpandableSectionToggle.test.tsx +28 -0
  161. package/src/components/ExpandableSection/examples/ExpandableSection.md +8 -0
  162. package/src/components/ExpandableSection/examples/ExpandableSectionCustomToggle.tsx +31 -16
  163. package/src/components/ExpandableSection/examples/ExpandableSectionWithHeading.tsx +89 -0
  164. package/src/components/Truncate/Truncate.tsx +5 -4
  165. package/src/components/Truncate/__tests__/Truncate.test.tsx +21 -3
  166. package/src/components/Truncate/__tests__/__snapshots__/Truncate.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.5.0-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.5.0-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.5.0-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.5.0-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.5.0-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.5.0-prerelease.4","private":true}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "6.5.0-prerelease.3",
3
+ "version": "6.5.0-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",
@@ -63,5 +63,5 @@
63
63
  "react": "^17 || ^18 || ^19",
64
64
  "react-dom": "^17 || ^18 || ^19"
65
65
  },
66
- "gitHead": "40a5015cf1eb4730bda9c5c15e328f6d544bc857"
66
+ "gitHead": "0f921f1c919501506e043b7c8b2febc71644b15a"
67
67
  }
@@ -0,0 +1,32 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { ClipboardCopy, ClipboardCopyVariant } from '../ClipboardCopy';
4
+
5
+ // This test file uses the real Truncate component for integration testing, instead of a mock
6
+
7
+ test('Tooltip appears on keyboard focus when using inline-compact variant with truncation', async () => {
8
+ const user = userEvent.setup();
9
+ const longText = 'This is a very long piece of content that should be truncated when the container is too small';
10
+
11
+ render(
12
+ <ClipboardCopy
13
+ variant={ClipboardCopyVariant.inlineCompact}
14
+ truncation={{ maxCharsDisplayed: 20 }}
15
+ data-testid="clipboard-copy"
16
+ >
17
+ {longText}
18
+ </ClipboardCopy>
19
+ );
20
+
21
+ expect(screen.queryByText(longText)).not.toBeInTheDocument();
22
+ expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
23
+
24
+ await user.tab();
25
+
26
+ const clipboardCopy = screen.getByTestId('clipboard-copy');
27
+ expect(clipboardCopy).toHaveFocus();
28
+
29
+ const tooltip = screen.getByRole('tooltip');
30
+ expect(tooltip).toBeInTheDocument();
31
+ expect(tooltip).toHaveTextContent(longText);
32
+ });
@@ -31,6 +31,8 @@ export interface DataListProps extends React.HTMLProps<HTMLUListElement> {
31
31
  selectedDataListItemId?: string;
32
32
  /** Flag indicating if DataList should have compact styling */
33
33
  isCompact?: boolean;
34
+ /** @beta Flag indicating if DataList should have plain styling with a transparent background */
35
+ isPlain?: boolean;
34
36
  /** Specifies the grid breakpoints */
35
37
  gridBreakpoint?: 'none' | 'always' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
36
38
  /** Determines which wrapping modifier to apply to the DataList */
@@ -59,6 +61,7 @@ export const DataListBase: React.FunctionComponent<DataListProps> = ({
59
61
  onSelectDataListItem,
60
62
  selectedDataListItemId = '',
61
63
  isCompact = false,
64
+ isPlain = false,
62
65
  gridBreakpoint = 'md',
63
66
  wrapModifier = null,
64
67
  onSelectableRowChange,
@@ -84,6 +87,7 @@ export const DataListBase: React.FunctionComponent<DataListProps> = ({
84
87
  className={css(
85
88
  styles.dataList,
86
89
  isCompact && styles.modifiers.compact,
90
+ isPlain && styles.modifiers.plain,
87
91
  gridBreakpointClasses[gridBreakpoint],
88
92
  wrapModifier && styles.modifiers[wrapModifier],
89
93
  className
@@ -38,6 +38,11 @@ import { DragDropSort, DragDropContainer, Droppable as NewDroppable } from '@pat
38
38
 
39
39
  ```ts file="./DataListCompact.tsx"
40
40
 
41
+ ```
42
+ ### Plain
43
+
44
+ ```ts file="./DataListPlain.tsx"
45
+
41
46
  ```
42
47
 
43
48
  ### Checkboxes, actions and additional cells
@@ -0,0 +1,32 @@
1
+ import { DataList, DataListItem, DataListItemRow, DataListItemCells, DataListCell } from '@patternfly/react-core';
2
+
3
+ export const DataListPlain: React.FunctionComponent = () => (
4
+ <DataList aria-label="Plain data list example" isPlain>
5
+ <DataListItem aria-labelledby="plain-item1">
6
+ <DataListItemRow>
7
+ <DataListItemCells
8
+ dataListCells={[
9
+ <DataListCell key="primary content">
10
+ <span id="plain-item1">Primary content</span>
11
+ </DataListCell>,
12
+ <DataListCell key="secondary content">Secondary content</DataListCell>
13
+ ]}
14
+ />
15
+ </DataListItemRow>
16
+ </DataListItem>
17
+ <DataListItem aria-labelledby="plain-item2">
18
+ <DataListItemRow>
19
+ <DataListItemCells
20
+ dataListCells={[
21
+ <DataListCell isFilled={false} key="secondary content fill">
22
+ <span id="plain-item2">Secondary content (pf-m-no-fill)</span>
23
+ </DataListCell>,
24
+ <DataListCell isFilled={false} alignRight key="secondary content align">
25
+ Secondary content (pf-m-align-right pf-m-no-fill)
26
+ </DataListCell>
27
+ ]}
28
+ />
29
+ </DataListItemRow>
30
+ </DataListItem>
31
+ </DataList>
32
+ );
@@ -74,6 +74,11 @@ export interface ExpandableSectionProps extends Omit<React.HTMLProps<HTMLDivElem
74
74
  * animation will not occur.
75
75
  */
76
76
  direction?: 'up' | 'down';
77
+ /** The HTML element to use for the toggle wrapper. Can be 'div' (default) or any heading level.
78
+ * When using heading elements, the button will be rendered inside the heading for proper semantics.
79
+ * This is useful when the toggle text should function as a heading in the document structure.
80
+ */
81
+ toggleWrapper?: 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
77
82
  }
78
83
 
79
84
  interface ExpandableSectionState {
@@ -218,6 +223,7 @@ class ExpandableSection extends Component<ExpandableSectionProps, ExpandableSect
218
223
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
219
224
  truncateMaxLines,
220
225
  direction,
226
+ toggleWrapper = 'div',
221
227
  ...props
222
228
  } = this.props;
223
229
 
@@ -250,9 +256,10 @@ class ExpandableSection extends Component<ExpandableSectionProps, ExpandableSect
250
256
 
251
257
  const computedToggleContent =
252
258
  typeof toggleContent === 'function' ? toggleContent(propOrStateIsExpanded) : toggleContent;
259
+ const ToggleWrapper = toggleWrapper as any;
253
260
 
254
261
  const expandableToggle = !isDetached && (
255
- <div className={`${styles.expandableSection}__toggle`}>
262
+ <ToggleWrapper className={`${styles.expandableSection}__toggle`}>
256
263
  <Button
257
264
  variant="link"
258
265
  {...(variant === ExpandableSectionVariant.truncate && { isInline: true })}
@@ -272,7 +279,7 @@ class ExpandableSection extends Component<ExpandableSectionProps, ExpandableSect
272
279
  >
273
280
  {computedToggleContent || computedToggleText}
274
281
  </Button>
275
- </div>
282
+ </ToggleWrapper>
276
283
  );
277
284
 
278
285
  return (
@@ -34,6 +34,11 @@ export interface ExpandableSectionToggleProps extends Omit<React.HTMLProps<HTMLD
34
34
  toggleAriaLabel?: string;
35
35
  /** Accessible name via space delimtted list of IDs for the expandable section toggle. */
36
36
  toggleAriaLabelledBy?: string;
37
+ /** The HTML element to use for the toggle wrapper. Can be 'div' (default) or any heading level.
38
+ * When using heading elements, the button will be rendered inside the heading for proper semantics.
39
+ * This is useful when the toggle text should function as a heading in the document structure.
40
+ */
41
+ toggleWrapper?: 'div' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
37
42
  }
38
43
 
39
44
  export const ExpandableSectionToggle: React.FunctionComponent<ExpandableSectionToggleProps> = ({
@@ -48,45 +53,50 @@ export const ExpandableSectionToggle: React.FunctionComponent<ExpandableSectionT
48
53
  isDetached,
49
54
  toggleAriaLabel,
50
55
  toggleAriaLabelledBy,
56
+ toggleWrapper = 'div',
51
57
  ...props
52
- }: ExpandableSectionToggleProps) => (
53
- <div
54
- className={css(
55
- styles.expandableSection,
56
- isExpanded && styles.modifiers.expanded,
57
- hasTruncatedContent && styles.modifiers.truncate,
58
- isDetached && 'pf-m-detached',
59
- className
60
- )}
61
- {...props}
62
- >
63
- <div className={`${styles.expandableSection}__toggle`}>
64
- <Button
65
- variant="link"
66
- {...(hasTruncatedContent && { isInline: true })}
67
- aria-expanded={isExpanded}
68
- aria-controls={contentId}
69
- onClick={() => onToggle(!isExpanded)}
70
- id={toggleId}
71
- {...(!hasTruncatedContent && {
72
- icon: (
73
- <span
74
- className={css(
75
- styles.expandableSectionToggleIcon,
76
- isExpanded && direction === 'up' && styles.modifiers.expandTop // TODO: next breaking change move this class to the outer styles.expandableSection wrapper
77
- )}
78
- >
79
- <AngleRightIcon />
80
- </span>
81
- )
82
- })}
83
- aria-label={toggleAriaLabel}
84
- aria-labelledby={toggleAriaLabelledBy}
85
- >
86
- {children}
87
- </Button>
58
+ }: ExpandableSectionToggleProps) => {
59
+ const ToggleWrapper = toggleWrapper as any;
60
+
61
+ return (
62
+ <div
63
+ className={css(
64
+ styles.expandableSection,
65
+ isExpanded && styles.modifiers.expanded,
66
+ hasTruncatedContent && styles.modifiers.truncate,
67
+ isDetached && 'pf-m-detached',
68
+ className
69
+ )}
70
+ {...props}
71
+ >
72
+ <ToggleWrapper className={`${styles.expandableSection}__toggle`}>
73
+ <Button
74
+ variant="link"
75
+ {...(hasTruncatedContent && { isInline: true })}
76
+ aria-expanded={isExpanded}
77
+ aria-controls={contentId}
78
+ onClick={() => onToggle(!isExpanded)}
79
+ id={toggleId}
80
+ {...(!hasTruncatedContent && {
81
+ icon: (
82
+ <span
83
+ className={css(
84
+ styles.expandableSectionToggleIcon,
85
+ isExpanded && direction === 'up' && styles.modifiers.expandTop // TODO: next breaking change move this class to the outer styles.expandableSection wrapper
86
+ )}
87
+ >
88
+ <AngleRightIcon />
89
+ </span>
90
+ )
91
+ })}
92
+ aria-label={toggleAriaLabel}
93
+ aria-labelledby={toggleAriaLabelledBy}
94
+ >
95
+ {children}
96
+ </Button>
97
+ </ToggleWrapper>
88
98
  </div>
89
- </div>
90
- );
99
+ );
100
+ };
91
101
 
92
102
  ExpandableSectionToggle.displayName = 'ExpandableSectionToggle';
@@ -208,6 +208,7 @@ test('Renders with aria-labelledby when toggleAriaLabelledBy is passed', () => {
208
208
 
209
209
  expect(screen.getByRole('button')).toHaveAccessibleName('Test label');
210
210
  });
211
+
211
212
  test('Renders toggleContent as a function in uncontrolled mode (collapsed)', () => {
212
213
  render(
213
214
  <ExpandableSection toggleContent={(isExpanded) => (isExpanded ? 'Hide details' : 'Show details')}>
@@ -242,3 +243,31 @@ test('Renders toggleContent as a function in controlled mode', () => {
242
243
 
243
244
  expect(screen.getByRole('button', { name: 'Collapse' })).toBeInTheDocument();
244
245
  });
246
+
247
+ test('Renders with default div wrapper when toggleWrapper is not specified', () => {
248
+ render(<ExpandableSection data-testid="test-id">Test content</ExpandableSection>);
249
+
250
+ const toggle = screen.getByRole('button').parentElement;
251
+ expect(toggle?.tagName).toBe('DIV');
252
+ });
253
+
254
+ test('Renders with h2 wrapper when toggleWrapper="h2"', () => {
255
+ render(
256
+ <ExpandableSection data-testid="test-id" toggleWrapper="h2">
257
+ Test content
258
+ </ExpandableSection>
259
+ );
260
+
261
+ expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument();
262
+ });
263
+
264
+ test('Renders with div wrapper when toggleWrapper="div"', () => {
265
+ render(
266
+ <ExpandableSection data-testid="test-id" toggleWrapper="div">
267
+ Test content
268
+ </ExpandableSection>
269
+ );
270
+
271
+ const toggle = screen.getByRole('button').parentElement;
272
+ expect(toggle?.tagName).toBe('DIV');
273
+ });
@@ -47,3 +47,31 @@ test('Renders with aria-labelledby when toggleAriaLabelledBy is passed', () => {
47
47
 
48
48
  expect(screen.getByRole('button')).toHaveAccessibleName('Test label');
49
49
  });
50
+
51
+ test('Renders with default div wrapper when toggleWrapper is not specified', () => {
52
+ render(<ExpandableSectionToggle data-testid="test-id">Toggle test</ExpandableSectionToggle>);
53
+
54
+ const toggle = screen.getByRole('button').parentElement;
55
+ expect(toggle?.tagName).toBe('DIV');
56
+ });
57
+
58
+ test('Renders with h2 wrapper when toggleWrapper="h2"', () => {
59
+ render(
60
+ <ExpandableSectionToggle data-testid="test-id" toggleWrapper="h2">
61
+ Toggle test
62
+ </ExpandableSectionToggle>
63
+ );
64
+
65
+ expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument();
66
+ });
67
+
68
+ test('Renders with div wrapper when toggleWrapper="div"', () => {
69
+ render(
70
+ <ExpandableSectionToggle data-testid="test-id" toggleWrapper="div">
71
+ Toggle test
72
+ </ExpandableSectionToggle>
73
+ );
74
+
75
+ const toggle = screen.getByRole('button').parentElement;
76
+ expect(toggle?.tagName).toBe('DIV');
77
+ });
@@ -70,6 +70,14 @@ By using the `toggleContent` prop, you can pass in content other than a simple s
70
70
 
71
71
  ```
72
72
 
73
+ ### With heading semantics
74
+
75
+ When the toggle text should function as a heading in the document structure, use the `toggleWrapper` prop to specify a heading element (h1-h6). This ensures proper semantic structure for screen readers and other assistive technologies. The component automatically uses a native button element when heading wrappers are used, allowing the heading styles to display properly.
76
+
77
+ ```ts file="ExpandableSectionWithHeading.tsx"
78
+
79
+ ```
80
+
73
81
  ### Truncate expansion
74
82
 
75
83
  By passing in `variant="truncate"`, the expandable content will be visible up to a maximum number of lines before being truncated, with the toggle revealing or hiding the truncated content. By default the expandable content will truncate after 3 lines, and this can be customized by also passing in the `truncateMaxLines` prop.
@@ -1,5 +1,5 @@
1
1
  import { useState } from 'react';
2
- import { ExpandableSection, Badge } from '@patternfly/react-core';
2
+ import { ExpandableSection, Badge, Stack, StackItem } from '@patternfly/react-core';
3
3
  import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon';
4
4
 
5
5
  export const ExpandableSectionCustomToggle: React.FunctionComponent = () => {
@@ -10,20 +10,35 @@ export const ExpandableSectionCustomToggle: React.FunctionComponent = () => {
10
10
  };
11
11
 
12
12
  return (
13
- <ExpandableSection
14
- toggleContent={
15
- <div>
16
- <span>You can also use icons </span>
17
- <CheckCircleIcon />
18
- <span> or badges </span>
19
- <Badge isRead={true}>4</Badge>
20
- <span> !</span>
21
- </div>
22
- }
23
- onToggle={onToggle}
24
- isExpanded={isExpanded}
25
- >
26
- This content is visible only when the component is expanded.
27
- </ExpandableSection>
13
+ <Stack hasGutter>
14
+ <StackItem>
15
+ <h3>Custom Toggle Content</h3>
16
+ <p>You can use custom content such as icons and badges in the toggle:</p>
17
+ <ExpandableSection
18
+ toggleContent={
19
+ <div>
20
+ <span>You can also use icons </span>
21
+ <CheckCircleIcon />
22
+ <span> or badges </span>
23
+ <Badge isRead={true}>4</Badge>
24
+ <span> !</span>
25
+ </div>
26
+ }
27
+ onToggle={onToggle}
28
+ isExpanded={isExpanded}
29
+ >
30
+ This content is visible only when the component is expanded.
31
+ </ExpandableSection>
32
+ </StackItem>
33
+
34
+ <StackItem>
35
+ <h3>Accessibility Note</h3>
36
+ <p>
37
+ <strong>Important:</strong> If you need the toggle text to function as a heading in the document structure, do
38
+ NOT put heading elements (h1-h6) inside the <code>toggleContent</code> prop, as this creates invalid HTML
39
+ structure. Instead, use the <code>toggleWrapper</code> prop.
40
+ </p>
41
+ </StackItem>
42
+ </Stack>
28
43
  );
29
44
  };
@@ -0,0 +1,89 @@
1
+ import { useState, MouseEvent } from 'react';
2
+ import { ExpandableSection, ExpandableSectionToggle, Stack, StackItem } from '@patternfly/react-core';
3
+ import CheckCircleIcon from '@patternfly/react-icons/dist/esm/icons/check-circle-icon';
4
+
5
+ export const ExpandableSectionWithHeading = () => {
6
+ const [isExpanded1, setIsExpanded1] = useState(false);
7
+ const [isExpanded2, setIsExpanded2] = useState(false);
8
+ const [isExpandedDetached, setIsExpandedDetached] = useState(false);
9
+
10
+ const onToggle1 = (_event: MouseEvent, isExpanded: boolean) => {
11
+ setIsExpanded1(isExpanded);
12
+ };
13
+
14
+ const onToggle2 = (_event: MouseEvent, isExpanded: boolean) => {
15
+ setIsExpanded2(isExpanded);
16
+ };
17
+
18
+ const onToggleDetached = (isExpanded: boolean) => {
19
+ setIsExpandedDetached(isExpanded);
20
+ };
21
+
22
+ return (
23
+ <Stack hasGutter>
24
+ <StackItem>
25
+ <h4>Document with Expandable Sections</h4>
26
+ <p>This demonstrates how to use expandable sections with proper heading semantics.</p>
27
+
28
+ {/* Using toggleWrapper prop for proper heading semantics */}
29
+ <ExpandableSection
30
+ toggleWrapper="h5"
31
+ toggleText="Toggle as a heading"
32
+ onToggle={onToggle1}
33
+ isExpanded={isExpanded1}
34
+ >
35
+ <p>
36
+ This content is visible only when the component is expanded. The toggle text above functions as a proper
37
+ heading in the document structure, which is important for screen readers and other assistive technologies.
38
+ </p>
39
+ <p>
40
+ When using the <code>toggleWrapper</code> prop with heading elements (h1-h6), the button is rendered inside
41
+ the heading element, maintaining proper semantic structure.
42
+ </p>
43
+ </ExpandableSection>
44
+ </StackItem>
45
+
46
+ <StackItem>
47
+ <h4>Detached Variant with Heading</h4>
48
+ <p>You can also use the detached variant with heading semantics:</p>
49
+
50
+ <ExpandableSectionToggle
51
+ toggleWrapper="h5"
52
+ toggleId="detached-heading-toggle"
53
+ contentId="detached-heading-content"
54
+ isExpanded={isExpandedDetached}
55
+ onToggle={onToggleDetached}
56
+ >
57
+ Detached Toggle with Heading
58
+ </ExpandableSectionToggle>
59
+
60
+ <ExpandableSection
61
+ isDetached
62
+ toggleId="detached-heading-toggle"
63
+ contentId="detached-heading-content"
64
+ isExpanded={isExpandedDetached}
65
+ >
66
+ <p>This is detached content that can be positioned anywhere in the DOM.</p>
67
+ </ExpandableSection>
68
+ </StackItem>
69
+
70
+ <StackItem>
71
+ <h4>Custom Content with Heading</h4>
72
+ <p>You can also use custom content within heading wrappers:</p>
73
+
74
+ <ExpandableSection
75
+ toggleWrapper="h5"
76
+ toggleContent={
77
+ <span>
78
+ <CheckCircleIcon /> Custom Heading Content with Icon
79
+ </span>
80
+ }
81
+ onToggle={onToggle2}
82
+ isExpanded={isExpanded2}
83
+ >
84
+ <p>This expandable section uses custom content with an icon inside a heading wrapper.</p>
85
+ </ExpandableSection>
86
+ </StackItem>
87
+ </Stack>
88
+ );
89
+ };
@@ -4,6 +4,7 @@ import { css } from '@patternfly/react-styles';
4
4
  import { Tooltip, TooltipPosition, TooltipProps } from '../Tooltip';
5
5
  import { getReferenceElement } from '../../helpers';
6
6
  import { getResizeObserver } from '../../helpers/resizeObserver';
7
+ import { debounce } from '../../helpers/util';
7
8
 
8
9
  export enum TruncatePosition {
9
10
  start = 'start',
@@ -130,12 +131,12 @@ const TruncateBase: React.FunctionComponent<TruncateProps> = ({
130
131
  const totalTextWidth = calculateTotalTextWidth(textElement, trailingNumChars, content);
131
132
  const textWidth = position === 'middle' ? totalTextWidth : textElement.scrollWidth;
132
133
 
133
- const handleResize = () => {
134
+ const debouncedHandleResize = debounce(() => {
134
135
  const parentWidth = getActualWidth(parentElement);
135
136
  setIsTruncated(textWidth >= parentWidth);
136
- };
137
+ }, 500);
137
138
 
138
- const observer = getResizeObserver(parentElement, handleResize);
139
+ const observer = getResizeObserver(parentElement, debouncedHandleResize);
139
140
 
140
141
  return () => {
141
142
  observer();
@@ -147,7 +148,7 @@ const TruncateBase: React.FunctionComponent<TruncateProps> = ({
147
148
  if (shouldRenderByMaxChars) {
148
149
  setIsTruncated(content.length > maxCharsDisplayed);
149
150
  }
150
- }, [shouldRenderByMaxChars]);
151
+ }, [shouldRenderByMaxChars, content.length, maxCharsDisplayed]);
151
152
 
152
153
  useEffect(() => {
153
154
  setShouldRenderByMaxChars(maxCharsDisplayed > 0);
@@ -1,12 +1,12 @@
1
- import { render, screen, within } from '@testing-library/react';
2
- import { Truncate, TruncatePosition } from '../Truncate';
1
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
2
+ import { Truncate } from '../Truncate';
3
3
  import styles from '@patternfly/react-styles/css/components/Truncate/truncate';
4
4
  import '@testing-library/jest-dom';
5
5
 
6
6
  jest.mock('../../Tooltip', () => ({
7
7
  Tooltip: ({ content, position, children, triggerRef, ...props }) => (
8
8
  <div data-testid="Tooltip-mock" {...props}>
9
- <div data-testid="Tooltip-mock-content-container">Test {content}</div>
9
+ <div>Test {content}</div>
10
10
  <p>{`position: ${position}`}</p>
11
11
  {children}
12
12
  </div>
@@ -242,3 +242,21 @@ describe('Truncation with maxCharsDisplayed', () => {
242
242
  expect(asFragment()).toMatchSnapshot();
243
243
  });
244
244
  });
245
+
246
+ test('Tooltip appears on keyboard focus when external triggerRef is provided (ClipboardCopy regression test)', () => {
247
+ const mockTriggerRef = { current: document.createElement('div') };
248
+
249
+ render(
250
+ <Truncate
251
+ content="This is a very long piece of content that should be truncated when the container is too small"
252
+ tooltipProps={{ triggerRef: mockTriggerRef }}
253
+ />
254
+ );
255
+
256
+ // Simulate keyboard focus on the external trigger element
257
+ fireEvent.focus(mockTriggerRef.current);
258
+
259
+ // The tooltip should be present and visible
260
+ const tooltip = screen.getByTestId('Tooltip-mock');
261
+ expect(tooltip).toBeInTheDocument();
262
+ });
@@ -5,9 +5,7 @@ exports[`Truncation with maxCharsDisplayed Matches snapshot with default positio
5
5
  <div
6
6
  data-testid="Tooltip-mock"
7
7
  >
8
- <div
9
- data-testid="Tooltip-mock-content-container"
10
- >
8
+ <div>
11
9
  Test Test truncation content
12
10
  </div>
13
11
  <p>
@@ -43,9 +41,7 @@ exports[`renders default truncation 1`] = `
43
41
  <div
44
42
  data-testid="Tooltip-mock"
45
43
  >
46
- <div
47
- data-testid="Tooltip-mock-content-container"
48
- >
44
+ <div>
49
45
  Test Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.
50
46
  </div>
51
47
  <p>
@@ -70,9 +66,7 @@ exports[`renders start truncation with &lrm; at start and end 1`] = `
70
66
  <div
71
67
  data-testid="Tooltip-mock"
72
68
  >
73
- <div
74
- data-testid="Tooltip-mock-content-container"
75
- >
69
+ <div>
76
70
  Test Vestibulum interdum risus et enim faucibus, sit amet molestie est accumsan.
77
71
  </div>
78
72
  <p>