@patternfly/react-core 6.3.1-prerelease.3 → 6.3.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 (144) hide show
  1. package/CHANGELOG.md +12 -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/Content/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/Drawer/package.json +1 -1
  28. package/dist/dynamic/components/Dropdown/package.json +1 -1
  29. package/dist/dynamic/components/DualListSelector/package.json +1 -1
  30. package/dist/dynamic/components/EmptyState/package.json +1 -1
  31. package/dist/dynamic/components/ExpandableSection/package.json +1 -1
  32. package/dist/dynamic/components/FileUpload/package.json +1 -1
  33. package/dist/dynamic/components/Form/package.json +1 -1
  34. package/dist/dynamic/components/FormSelect/package.json +1 -1
  35. package/dist/dynamic/components/HelperText/package.json +1 -1
  36. package/dist/dynamic/components/Hint/package.json +1 -1
  37. package/dist/dynamic/components/Icon/package.json +1 -1
  38. package/dist/dynamic/components/InputGroup/package.json +1 -1
  39. package/dist/dynamic/components/JumpLinks/package.json +1 -1
  40. package/dist/dynamic/components/Label/package.json +1 -1
  41. package/dist/dynamic/components/List/package.json +1 -1
  42. package/dist/dynamic/components/LoginPage/package.json +1 -1
  43. package/dist/dynamic/components/Masthead/package.json +1 -1
  44. package/dist/dynamic/components/Menu/package.json +1 -1
  45. package/dist/dynamic/components/MenuToggle/package.json +1 -1
  46. package/dist/dynamic/components/Modal/package.json +1 -1
  47. package/dist/dynamic/components/MultipleFileUpload/package.json +1 -1
  48. package/dist/dynamic/components/Nav/package.json +1 -1
  49. package/dist/dynamic/components/NotificationBadge/package.json +1 -1
  50. package/dist/dynamic/components/NotificationDrawer/package.json +1 -1
  51. package/dist/dynamic/components/NumberInput/package.json +1 -1
  52. package/dist/dynamic/components/OverflowMenu/package.json +1 -1
  53. package/dist/dynamic/components/Page/package.json +1 -1
  54. package/dist/dynamic/components/Pagination/package.json +1 -1
  55. package/dist/dynamic/components/Panel/package.json +1 -1
  56. package/dist/dynamic/components/Popover/package.json +1 -1
  57. package/dist/dynamic/components/Progress/package.json +1 -1
  58. package/dist/dynamic/components/ProgressStepper/package.json +1 -1
  59. package/dist/dynamic/components/Radio/package.json +1 -1
  60. package/dist/dynamic/components/SearchInput/package.json +1 -1
  61. package/dist/dynamic/components/Select/package.json +1 -1
  62. package/dist/dynamic/components/Sidebar/package.json +1 -1
  63. package/dist/dynamic/components/SimpleList/package.json +1 -1
  64. package/dist/dynamic/components/Skeleton/package.json +1 -1
  65. package/dist/dynamic/components/SkipToContent/package.json +1 -1
  66. package/dist/dynamic/components/Slider/package.json +1 -1
  67. package/dist/dynamic/components/Spinner/package.json +1 -1
  68. package/dist/dynamic/components/Switch/package.json +1 -1
  69. package/dist/dynamic/components/Tabs/package.json +1 -1
  70. package/dist/dynamic/components/TextArea/package.json +1 -1
  71. package/dist/dynamic/components/TextInput/package.json +1 -1
  72. package/dist/dynamic/components/TextInputGroup/package.json +1 -1
  73. package/dist/dynamic/components/TimePicker/package.json +1 -1
  74. package/dist/dynamic/components/Timestamp/package.json +1 -1
  75. package/dist/dynamic/components/Title/package.json +1 -1
  76. package/dist/dynamic/components/ToggleGroup/package.json +1 -1
  77. package/dist/dynamic/components/Toolbar/package.json +1 -1
  78. package/dist/dynamic/components/Tooltip/package.json +1 -1
  79. package/dist/dynamic/components/TreeView/package.json +1 -1
  80. package/dist/dynamic/components/Truncate/package.json +1 -1
  81. package/dist/dynamic/components/Wizard/hooks/package.json +1 -1
  82. package/dist/dynamic/components/Wizard/package.json +1 -1
  83. package/dist/dynamic/deprecated/components/Chip/package.json +1 -1
  84. package/dist/dynamic/deprecated/components/DragDrop/package.json +1 -1
  85. package/dist/dynamic/deprecated/components/DualListSelector/package.json +1 -1
  86. package/dist/dynamic/deprecated/components/Modal/package.json +1 -1
  87. package/dist/dynamic/deprecated/components/Tile/package.json +1 -1
  88. package/dist/dynamic/deprecated/components/Wizard/package.json +1 -1
  89. package/dist/dynamic/deprecated/components/package.json +1 -1
  90. package/dist/dynamic/helpers/FocusTrap/FocusTrap/package.json +1 -1
  91. package/dist/dynamic/helpers/GenerateId/GenerateId/package.json +1 -1
  92. package/dist/dynamic/helpers/KeyboardHandler/package.json +1 -1
  93. package/dist/dynamic/helpers/OUIA/ouia/package.json +1 -1
  94. package/dist/dynamic/helpers/Popper/Popper/package.json +1 -1
  95. package/dist/dynamic/helpers/constants/package.json +1 -1
  96. package/dist/dynamic/helpers/datetimeUtils/package.json +1 -1
  97. package/dist/dynamic/helpers/fileUtils/package.json +1 -1
  98. package/dist/dynamic/helpers/htmlConstants/package.json +1 -1
  99. package/dist/dynamic/helpers/package.json +1 -1
  100. package/dist/dynamic/helpers/resizeObserver/package.json +1 -1
  101. package/dist/dynamic/helpers/typeUtils/package.json +1 -1
  102. package/dist/dynamic/helpers/useInterval/package.json +1 -1
  103. package/dist/dynamic/helpers/useIsomorphicLayout/package.json +1 -1
  104. package/dist/dynamic/helpers/useUnmountEffect/package.json +1 -1
  105. package/dist/dynamic/helpers/util/package.json +1 -1
  106. package/dist/dynamic/layouts/Bullseye/package.json +1 -1
  107. package/dist/dynamic/layouts/Flex/package.json +1 -1
  108. package/dist/dynamic/layouts/Gallery/package.json +1 -1
  109. package/dist/dynamic/layouts/Grid/package.json +1 -1
  110. package/dist/dynamic/layouts/Level/package.json +1 -1
  111. package/dist/dynamic/layouts/Split/package.json +1 -1
  112. package/dist/dynamic/layouts/Stack/package.json +1 -1
  113. package/dist/dynamic/styles/package.json +1 -1
  114. package/dist/esm/components/JumpLinks/JumpLinks.d.ts +2 -0
  115. package/dist/esm/components/JumpLinks/JumpLinks.d.ts.map +1 -1
  116. package/dist/esm/components/JumpLinks/JumpLinks.js +15 -3
  117. package/dist/esm/components/JumpLinks/JumpLinks.js.map +1 -1
  118. package/dist/esm/components/Tooltip/Tooltip.d.ts.map +1 -1
  119. package/dist/esm/components/Tooltip/Tooltip.js +68 -3
  120. package/dist/esm/components/Tooltip/Tooltip.js.map +1 -1
  121. package/dist/js/components/JumpLinks/JumpLinks.d.ts +2 -0
  122. package/dist/js/components/JumpLinks/JumpLinks.d.ts.map +1 -1
  123. package/dist/js/components/JumpLinks/JumpLinks.js +14 -2
  124. package/dist/js/components/JumpLinks/JumpLinks.js.map +1 -1
  125. package/dist/js/components/Tooltip/Tooltip.d.ts.map +1 -1
  126. package/dist/js/components/Tooltip/Tooltip.js +68 -3
  127. package/dist/js/components/Tooltip/Tooltip.js.map +1 -1
  128. package/dist/umd/assets/{output-CtvIYag_.css → output-CBa3Ztgf.css} +20543 -20543
  129. package/dist/umd/react-core.min.js +3 -3
  130. package/helpers/package.json +1 -1
  131. package/layouts/package.json +1 -1
  132. package/next/package.json +1 -1
  133. package/package.json +2 -2
  134. package/src/components/JumpLinks/JumpLinks.tsx +33 -5
  135. package/src/components/JumpLinks/__tests__/JumpLinks.test.tsx +196 -1
  136. package/src/components/JumpLinks/__tests__/__snapshots__/JumpLinks.test.tsx.snap +9 -9
  137. package/src/components/JumpLinks/examples/JumpLinksBasic.tsx +1 -1
  138. package/src/components/JumpLinks/examples/JumpLinksExpandableVerticalWithSubsection.tsx +2 -2
  139. package/src/components/JumpLinks/examples/JumpLinksVertical.tsx +1 -1
  140. package/src/components/JumpLinks/examples/JumpLinksVerticalWithLabel.tsx +1 -1
  141. package/src/components/JumpLinks/examples/JumpLinksWithCenteredList.tsx +1 -1
  142. package/src/components/JumpLinks/examples/JumpLinksWithLabel.tsx +2 -2
  143. package/src/components/Tooltip/Tooltip.tsx +76 -3
  144. package/src/components/Tooltip/__tests__/Tooltip.test.tsx +126 -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":"6.3.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.3.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.3.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.3.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.3.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.3.1-prerelease.4","private":true}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patternfly/react-core",
3
- "version": "6.3.1-prerelease.3",
3
+ "version": "6.3.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",
@@ -63,5 +63,5 @@
63
63
  "react": "^17 || ^18 || ^19",
64
64
  "react-dom": "^17 || ^18 || ^19"
65
65
  },
66
- "gitHead": "18c9c578856041efeb380437b915e3fd51c9c2df"
66
+ "gitHead": "559712b7e2d3720b833a20caed00a2991f361243"
67
67
  }
@@ -7,7 +7,7 @@ import cssToggleDisplayVar from '@patternfly/react-tokens/dist/esm/c_jump_links_
7
7
  import { Button } from '../Button';
8
8
  import { JumpLinksItem, JumpLinksItemProps } from './JumpLinksItem';
9
9
  import { JumpLinksList } from './JumpLinksList';
10
- import { canUseDOM, formatBreakpointMods } from '../../helpers/util';
10
+ import { canUseDOM, formatBreakpointMods, getUniqueId } from '../../helpers/util';
11
11
 
12
12
  export interface JumpLinksProps extends Omit<React.HTMLProps<HTMLElement>, 'label'> {
13
13
  /** Whether to center children. */
@@ -47,6 +47,8 @@ export interface JumpLinksProps extends Omit<React.HTMLProps<HTMLElement>, 'labe
47
47
  className?: string;
48
48
  /** Whether the current entry in the navigation history should be replaced when a JumpLinksItem is clicked. By default a new entry will be pushed to the navigation history. */
49
49
  shouldReplaceNavHistory?: boolean;
50
+ /** Custom ID applied to label if alwaysShowLabel is applied, or expandable toggle. This is used for internal logic related to aria-label and aria-labelledby */
51
+ labelId?: string;
50
52
  }
51
53
 
52
54
  // Recursively find JumpLinkItems and return an array of all their scrollNodes
@@ -94,6 +96,7 @@ export const JumpLinks: React.FunctionComponent<JumpLinksProps> = ({
94
96
  toggleAriaLabel = 'Toggle jump links',
95
97
  className,
96
98
  shouldReplaceNavHistory = false,
99
+ labelId,
97
100
  ...props
98
101
  }: JumpLinksProps) => {
99
102
  const hasScrollSpy = Boolean(scrollableRef || scrollableSelector);
@@ -106,6 +109,15 @@ export const JumpLinks: React.FunctionComponent<JumpLinksProps> = ({
106
109
 
107
110
  let scrollableElement: HTMLElement;
108
111
 
112
+ if (!label && !ariaLabel) {
113
+ // eslint-disable-next-line no-console
114
+ console.warn('JumpLinks: for accessibility reasons, an aria-label should be specified if no label is provided');
115
+ }
116
+ if (!label && !toggleAriaLabel && expandable) {
117
+ // eslint-disable-next-line no-console
118
+ console.warn('JumpLinks: for accessibility reasons, a toggleAriaLabel should be specified if no label is provided');
119
+ }
120
+
109
121
  const getScrollableElement = () => {
110
122
  if (scrollableRef) {
111
123
  if (scrollableRef instanceof HTMLElement) {
@@ -232,6 +244,11 @@ export const JumpLinks: React.FunctionComponent<JumpLinksProps> = ({
232
244
  return child;
233
245
  });
234
246
 
247
+ const id = labelId ?? getUniqueId();
248
+ const hasAriaLabelledBy = expandable || (label && alwaysShowLabel);
249
+ const computedAriaLabel = hasAriaLabelledBy ? null : ariaLabel;
250
+ const computedAriaLabelledBy = hasAriaLabelledBy ? id : null;
251
+
235
252
  return (
236
253
  <nav
237
254
  className={css(
@@ -242,8 +259,9 @@ export const JumpLinks: React.FunctionComponent<JumpLinksProps> = ({
242
259
  isExpanded && styles.modifiers.expanded,
243
260
  className
244
261
  )}
245
- aria-label={ariaLabel}
262
+ aria-label={computedAriaLabel}
246
263
  ref={navRef}
264
+ aria-labelledby={computedAriaLabelledBy}
247
265
  {...props}
248
266
  >
249
267
  <div className={styles.jumpLinksMain}>
@@ -253,21 +271,31 @@ export const JumpLinks: React.FunctionComponent<JumpLinksProps> = ({
253
271
  <Button
254
272
  variant="plain"
255
273
  onClick={() => setIsExpanded(!isExpanded)}
256
- aria-label={toggleAriaLabel}
274
+ aria-label={label ? null : toggleAriaLabel}
257
275
  aria-expanded={isExpanded}
258
276
  icon={
259
277
  <span className={styles.jumpLinksToggleIcon}>
260
278
  <AngleRightIcon />
261
279
  </span>
262
280
  }
281
+ id={id}
263
282
  >
264
283
  {label && label}
265
284
  </Button>
266
285
  </div>
267
286
  )}
268
- {label && alwaysShowLabel && <div className={css(styles.jumpLinksLabel)}>{label}</div>}
287
+ {label && alwaysShowLabel && !expandable && (
288
+ <div className={css(styles.jumpLinksLabel)} id={id}>
289
+ {label}
290
+ </div>
291
+ )}
269
292
  </div>
270
- <ul className={styles.jumpLinksList} role="list">
293
+ <ul
294
+ aria-label={computedAriaLabel}
295
+ aria-labelledby={computedAriaLabelledBy}
296
+ className={styles.jumpLinksList}
297
+ role="list"
298
+ >
271
299
  {cloneChildren(children)}
272
300
  </ul>
273
301
  </div>
@@ -1,7 +1,10 @@
1
- import { render } from '@testing-library/react';
1
+ import { render, screen } from '@testing-library/react';
2
2
  import { JumpLinks } from '../JumpLinks';
3
3
  import { JumpLinksItem } from '../JumpLinksItem';
4
4
  import { JumpLinksList } from '../JumpLinksList';
5
+ import * as utils from '../../../helpers/util';
6
+
7
+ jest.spyOn(utils, 'getUniqueId').mockReturnValue('unique_id_mock');
5
8
 
6
9
  test('simple jumplinks', () => {
7
10
  const { asFragment } = render(
@@ -75,3 +78,195 @@ test('expandable vertical with subsection', () => {
75
78
  );
76
79
  expect(asFragment()).toMatchSnapshot();
77
80
  });
81
+
82
+ // Revamped tests begin here
83
+
84
+ const jumpLinksItems = (
85
+ <>
86
+ <JumpLinksItem href="#">Inactive section</JumpLinksItem>
87
+ <JumpLinksItem href="#" isActive>
88
+ Active section
89
+ </JumpLinksItem>
90
+ <JumpLinksItem href="#">Inactive section</JumpLinksItem>
91
+ </>
92
+ );
93
+
94
+ const expandableJumpLinksItems = (
95
+ <>
96
+ <JumpLinksItem href="#">Inactive section</JumpLinksItem>
97
+ <JumpLinksItem href="#">
98
+ Section with active subsection
99
+ <JumpLinksList>
100
+ <JumpLinksItem href="#" isActive>
101
+ Active subsection
102
+ </JumpLinksItem>
103
+ <JumpLinksItem href="#">Inactive subsection</JumpLinksItem>
104
+ <JumpLinksItem href="#">Inactive subsection</JumpLinksItem>
105
+ </JumpLinksList>
106
+ </JumpLinksItem>
107
+ <JumpLinksItem href="#">Inactive section</JumpLinksItem>
108
+ <JumpLinksItem href="#">Inactive section</JumpLinksItem>
109
+ </>
110
+ );
111
+
112
+ test('renders label be default', () => {
113
+ render(<JumpLinks label="Jump to section">{jumpLinksItems}</JumpLinks>);
114
+ expect(screen.getByText(/Jump to section/i)).toBeTruthy();
115
+ });
116
+
117
+ test('does not render label when alwaysShowLabel is false', () => {
118
+ render(
119
+ <JumpLinks label="Jump to section" alwaysShowLabel={false}>
120
+ {jumpLinksItems}
121
+ </JumpLinks>
122
+ );
123
+ expect(screen.queryByText(/Jump to section/i)).toBeFalsy();
124
+ });
125
+
126
+ test('uses aria-labelledby over aria-label when label and alwaysShowLabel are passed in', () => {
127
+ render(<JumpLinks label="Jump to section">{expandableJumpLinksItems}</JumpLinks>);
128
+ const navigation = screen.getByRole('navigation', { name: /Jump to section/i });
129
+ expect(navigation).toHaveAttribute('aria-labelledby');
130
+ expect(navigation).not.toHaveAttribute('aria-label');
131
+ });
132
+
133
+ test('uses aria-labelledby over aria-label when expandable is passed in', () => {
134
+ render(<JumpLinks expandable={{ default: 'expandable' }}>{expandableJumpLinksItems}</JumpLinks>);
135
+ const navigation = screen.getByRole('navigation', { name: /Toggle jump links/i });
136
+ expect(navigation).toHaveAttribute('aria-labelledby');
137
+ expect(navigation).not.toHaveAttribute('aria-label');
138
+ });
139
+
140
+ test('random id is used with aria-labelledby by default in expandable case', () => {
141
+ render(<JumpLinks expandable={{ default: 'expandable' }}>{expandableJumpLinksItems}</JumpLinks>);
142
+ const navigation = screen.getByRole('navigation', { name: /Toggle jump links/i });
143
+ expect(navigation).toHaveAttribute('aria-labelledby', 'unique_id_mock');
144
+ });
145
+
146
+ test('random id is used with aria-labelledby by default in label case', () => {
147
+ render(
148
+ <JumpLinks label="Jump to section" alwaysShowLabel>
149
+ {expandableJumpLinksItems}
150
+ </JumpLinks>
151
+ );
152
+ const navigation = screen.getByRole('navigation', { name: /Jump to section/i });
153
+ expect(navigation).toHaveAttribute('aria-labelledby', 'unique_id_mock');
154
+ });
155
+
156
+ test('custom labelId is used with aria-labelledby when it is passed in in expandable case', () => {
157
+ render(
158
+ <JumpLinks labelId="custom-id" expandable={{ default: 'expandable' }}>
159
+ {expandableJumpLinksItems}
160
+ </JumpLinks>
161
+ );
162
+ const navigation = screen.getByRole('navigation', { name: /Toggle jump links/i });
163
+ expect(navigation).toHaveAttribute('aria-labelledby', 'custom-id');
164
+ });
165
+
166
+ test('custom labelId is used with aria-labelledby when it is passed in in label case', () => {
167
+ render(
168
+ <JumpLinks label="Jump to section" labelId="custom-id" alwaysShowLabel>
169
+ {expandableJumpLinksItems}
170
+ </JumpLinks>
171
+ );
172
+ const navigation = screen.getByRole('navigation', { name: /Jump to section/i });
173
+ expect(navigation).toHaveAttribute('aria-labelledby', 'custom-id');
174
+ });
175
+
176
+ test('uses aria-label instead of aria-labelledby by default', () => {
177
+ render(<JumpLinks aria-label="Custom aria label">{jumpLinksItems}</JumpLinks>);
178
+ const navigation = screen.getByRole('navigation', { name: /Custom aria label/i });
179
+ expect(navigation).toHaveAttribute('aria-label', 'Custom aria label');
180
+ expect(navigation).not.toHaveAttribute('aria-labelledby');
181
+ });
182
+
183
+ test('uses aria-label instead of aria-labelledby when label is provided but alwaysShowLabel is false', () => {
184
+ render(
185
+ <JumpLinks label="Jump to section" aria-label="Custom aria label" alwaysShowLabel={false}>
186
+ {jumpLinksItems}
187
+ </JumpLinks>
188
+ );
189
+ const navigation = screen.getByRole('navigation', { name: /Custom aria label/i });
190
+ expect(navigation).toHaveAttribute('aria-label', 'Custom aria label');
191
+ expect(navigation).not.toHaveAttribute('aria-labelledby');
192
+ });
193
+
194
+ test('aria-labelledby is used with provided labelId even when aria-label is also provided in expandable case', () => {
195
+ render(
196
+ <JumpLinks labelId="custom-id" aria-label="Custom aria label" expandable={{ default: 'expandable' }}>
197
+ {expandableJumpLinksItems}
198
+ </JumpLinks>
199
+ );
200
+ const navigation = screen.getByRole('navigation');
201
+ expect(navigation).toHaveAttribute('aria-labelledby', 'custom-id');
202
+ expect(navigation).not.toHaveAttribute('aria-label');
203
+ });
204
+
205
+ test('aria-labelledby is used with provided labelId even when aria-label is also provided in label case', () => {
206
+ render(
207
+ <JumpLinks labelId="custom-id" aria-label="Custom aria label" label="Jump to section">
208
+ {jumpLinksItems}
209
+ </JumpLinks>
210
+ );
211
+ const navigation = screen.getByRole('navigation');
212
+ expect(navigation).toHaveAttribute('aria-labelledby', 'custom-id');
213
+ expect(navigation).not.toHaveAttribute('aria-label');
214
+ });
215
+
216
+ test('does not throw error when there is a label passed in"', () => {
217
+ const warnMock = jest.fn() as any;
218
+ global.console = { warn: warnMock } as any;
219
+ render(
220
+ <JumpLinks alwaysShowLabel label="Jump to section">
221
+ {jumpLinksItems}
222
+ </JumpLinks>
223
+ );
224
+ expect(warnMock).not.toHaveBeenCalled();
225
+ });
226
+
227
+ test('does not throw error when there is an aria-label passed in"', () => {
228
+ const warnMock = jest.fn() as any;
229
+ global.console = { warn: warnMock } as any;
230
+ render(
231
+ <JumpLinks alwaysShowLabel aria-label="Jump to section">
232
+ {jumpLinksItems}
233
+ </JumpLinks>
234
+ );
235
+ expect(warnMock).not.toHaveBeenCalled();
236
+ });
237
+
238
+ test('throws error when no label or ariaLabel are passed in', () => {
239
+ const warnMock = jest.fn() as any;
240
+ global.console = { warn: warnMock } as any;
241
+ render(<JumpLinks alwaysShowLabel>{jumpLinksItems}</JumpLinks>);
242
+ expect(warnMock).toHaveBeenCalled();
243
+ });
244
+
245
+ test('does not throw error when there is a toggleAriaLabel and expandable prop passed in', () => {
246
+ const warnMock = jest.fn() as any;
247
+ global.console = { warn: warnMock } as any;
248
+ render(
249
+ <JumpLinks label="Jump to section" toggleAriaLabel="Jump to section" expandable={{ default: 'expandable' }}>
250
+ {expandableJumpLinksItems}
251
+ </JumpLinks>
252
+ );
253
+ expect(warnMock).not.toHaveBeenCalled();
254
+ });
255
+
256
+ test('does not throw error when there is an aria-label and expandable prop passed in', () => {
257
+ const warnMock = jest.fn() as any;
258
+ global.console = { warn: warnMock } as any;
259
+ render(
260
+ <JumpLinks aria-label="Jump to section" expandable={{ default: 'expandable' }}>
261
+ {expandableJumpLinksItems}
262
+ </JumpLinks>
263
+ );
264
+ expect(warnMock).not.toHaveBeenCalled();
265
+ });
266
+
267
+ test('throws error when there is no toggle aria-label or aria-label but expandable is passed in', () => {
268
+ const warnMock = jest.fn() as any;
269
+ global.console = { warn: warnMock } as any;
270
+ render(<JumpLinks expandable={{ default: 'expandable' }}>{expandableJumpLinksItems}</JumpLinks>);
271
+ expect(warnMock).toHaveBeenCalled();
272
+ });
@@ -3,7 +3,7 @@
3
3
  exports[`expandable vertical with subsection 1`] = `
4
4
  <DocumentFragment>
5
5
  <nav
6
- aria-label="Jump to section"
6
+ aria-labelledby="unique_id_mock"
7
7
  class="pf-v6-c-jump-links pf-m-vertical pf-m-expandable"
8
8
  >
9
9
  <div
@@ -17,11 +17,11 @@ exports[`expandable vertical with subsection 1`] = `
17
17
  >
18
18
  <button
19
19
  aria-expanded="false"
20
- aria-label="Toggle jump links"
21
20
  class="pf-v6-c-button pf-m-plain"
22
21
  data-ouia-component-id="OUIA-Generated-Button-plain-1"
23
22
  data-ouia-component-type="PF6/Button"
24
23
  data-ouia-safe="true"
24
+ id="unique_id_mock"
25
25
  type="button"
26
26
  >
27
27
  <span
@@ -52,13 +52,9 @@ exports[`expandable vertical with subsection 1`] = `
52
52
  </span>
53
53
  </button>
54
54
  </div>
55
- <div
56
- class="pf-v6-c-jump-links__label"
57
- >
58
- Jump to section
59
- </div>
60
55
  </div>
61
56
  <ul
57
+ aria-labelledby="unique_id_mock"
62
58
  class="pf-v6-c-jump-links__list"
63
59
  role="list"
64
60
  >
@@ -349,7 +345,7 @@ exports[`jumplinks centered 1`] = `
349
345
  exports[`jumplinks with label 1`] = `
350
346
  <DocumentFragment>
351
347
  <nav
352
- aria-label="Jump to section"
348
+ aria-labelledby="unique_id_mock"
353
349
  class="pf-v6-c-jump-links pf-m-center"
354
350
  >
355
351
  <div
@@ -360,11 +356,13 @@ exports[`jumplinks with label 1`] = `
360
356
  >
361
357
  <div
362
358
  class="pf-v6-c-jump-links__label"
359
+ id="unique_id_mock"
363
360
  >
364
361
  Jump to section
365
362
  </div>
366
363
  </div>
367
364
  <ul
365
+ aria-labelledby="unique_id_mock"
368
366
  class="pf-v6-c-jump-links__list"
369
367
  role="list"
370
368
  >
@@ -550,7 +548,7 @@ exports[`simple jumplinks 1`] = `
550
548
  exports[`vertical with label 1`] = `
551
549
  <DocumentFragment>
552
550
  <nav
553
- aria-label="Jump to section"
551
+ aria-labelledby="unique_id_mock"
554
552
  class="pf-v6-c-jump-links pf-m-vertical"
555
553
  >
556
554
  <div
@@ -561,11 +559,13 @@ exports[`vertical with label 1`] = `
561
559
  >
562
560
  <div
563
561
  class="pf-v6-c-jump-links__label"
562
+ id="unique_id_mock"
564
563
  >
565
564
  Jump to section
566
565
  </div>
567
566
  </div>
568
567
  <ul
568
+ aria-labelledby="unique_id_mock"
569
569
  class="pf-v6-c-jump-links__list"
570
570
  role="list"
571
571
  >
@@ -1,7 +1,7 @@
1
1
  import { JumpLinks, JumpLinksItem } from '@patternfly/react-core';
2
2
 
3
3
  export const JumpLinksBasic: React.FunctionComponent = () => (
4
- <JumpLinks>
4
+ <JumpLinks aria-label="Jump to basic example sections">
5
5
  <JumpLinksItem href="#">Inactive section</JumpLinksItem>
6
6
  <JumpLinksItem href="#" isActive>
7
7
  Active section
@@ -1,11 +1,11 @@
1
1
  import { JumpLinks, JumpLinksItem, JumpLinksList } from '@patternfly/react-core';
2
2
 
3
3
  export const JumpLinksExpandableVerticalWithSubsection: React.FunctionComponent = () => (
4
- <JumpLinks isVertical label="Jump to section" expandable={{ default: 'expandable' }}>
4
+ <JumpLinks isVertical label="Jump to expandable vertical section" expandable={{ default: 'expandable' }}>
5
5
  <JumpLinksItem href="#">Inactive section</JumpLinksItem>
6
6
  <JumpLinksItem href="#">
7
7
  Section with active subsection
8
- <JumpLinksList>
8
+ <JumpLinksList aria-label="Expandable vertical section subsection">
9
9
  <JumpLinksItem href="#" isActive>
10
10
  Active subsection
11
11
  </JumpLinksItem>
@@ -1,7 +1,7 @@
1
1
  import { JumpLinks, JumpLinksItem } from '@patternfly/react-core';
2
2
 
3
3
  export const JumpLinksVertical: React.FunctionComponent = () => (
4
- <JumpLinks isVertical>
4
+ <JumpLinks isVertical aria-label="Jump to vertical example sections">
5
5
  <JumpLinksItem href="#">Inactive section</JumpLinksItem>
6
6
  <JumpLinksItem href="#" isActive>
7
7
  Active section
@@ -1,7 +1,7 @@
1
1
  import { JumpLinks, JumpLinksItem } from '@patternfly/react-core';
2
2
 
3
3
  export const JumpLinksVerticalWithLabel: React.FunctionComponent = () => (
4
- <JumpLinks isVertical label="Jump to section">
4
+ <JumpLinks isVertical label="Jump to vertical example sections with labels">
5
5
  <JumpLinksItem href="#">Inactive section</JumpLinksItem>
6
6
  <JumpLinksItem href="#" isActive>
7
7
  Active section
@@ -1,7 +1,7 @@
1
1
  import { JumpLinks, JumpLinksItem } from '@patternfly/react-core';
2
2
 
3
3
  export const JumpLinksWithCenteredList: React.FunctionComponent = () => (
4
- <JumpLinks isCentered>
4
+ <JumpLinks isCentered aria-label="Jump to centered example sections">
5
5
  <JumpLinksItem href="#">Inactive section</JumpLinksItem>
6
6
  <JumpLinksItem href="#" isActive>
7
7
  Active section
@@ -2,14 +2,14 @@ import { JumpLinks, JumpLinksItem } from '@patternfly/react-core';
2
2
 
3
3
  export const JumpLinksWithLabel: React.FunctionComponent = () => (
4
4
  <>
5
- <JumpLinks label="Jump to section">
5
+ <JumpLinks label="Jump to first section in jump links with label example">
6
6
  <JumpLinksItem href="#">Inactive section</JumpLinksItem>
7
7
  <JumpLinksItem href="#" isActive>
8
8
  Active section
9
9
  </JumpLinksItem>
10
10
  <JumpLinksItem href="#">Inactive section</JumpLinksItem>
11
11
  </JumpLinks>
12
- <JumpLinks isCentered label="Jump to section">
12
+ <JumpLinks isCentered label="Jump to second section in jump links with label example">
13
13
  <JumpLinksItem href="#">Inactive section</JumpLinksItem>
14
14
  <JumpLinksItem href="#" isActive>
15
15
  Active section
@@ -174,6 +174,75 @@ export const Tooltip: React.FunctionComponent<TooltipProps> = ({
174
174
  const [visible, setVisible] = useState(false);
175
175
  const popperRef = createRef<HTMLDivElement>();
176
176
 
177
+ const getTriggerRefElement = (): HTMLElement | null => {
178
+ if (typeof triggerRef === 'function') {
179
+ return triggerRef();
180
+ } else if (triggerRef && typeof triggerRef === 'object' && 'current' in triggerRef) {
181
+ return triggerRef.current;
182
+ } else if (triggerRef instanceof HTMLElement) {
183
+ return triggerRef;
184
+ }
185
+ return null;
186
+ };
187
+
188
+ const getAriaAttributeName = () => (aria !== 'none' ? `aria-${aria}` : null);
189
+
190
+ const addAriaToRefElement = (element: HTMLElement): void => {
191
+ const attributeName = getAriaAttributeName();
192
+ if (!element || !attributeName) {
193
+ return;
194
+ }
195
+
196
+ const existingAria = element.getAttribute(attributeName);
197
+ if (!existingAria || !existingAria.includes(id)) {
198
+ const newAria = existingAria ? `${existingAria} ${id}` : id;
199
+ element.setAttribute(attributeName, newAria);
200
+ }
201
+ };
202
+
203
+ const removeAriaFromRefElement = (element: HTMLElement): void => {
204
+ const attributeName = getAriaAttributeName();
205
+ if (!element || !attributeName) {
206
+ return;
207
+ }
208
+
209
+ const existingAria = element.getAttribute(attributeName);
210
+ if (!existingAria) {
211
+ return;
212
+ }
213
+ const newAria = existingAria.replace(new RegExp(`\\b${id}\\b`, 'g'), '').trim();
214
+ if (newAria) {
215
+ element.setAttribute(attributeName, newAria);
216
+ } else {
217
+ element.removeAttribute(attributeName);
218
+ }
219
+ };
220
+
221
+ const updateTriggerElementAria = (shouldAddAria: boolean): void => {
222
+ if (aria === 'none' || !triggerRef || children) {
223
+ return;
224
+ }
225
+
226
+ const triggerElement = getTriggerRefElement();
227
+ if (!triggerElement) {
228
+ return;
229
+ }
230
+
231
+ if (shouldAddAria) {
232
+ addAriaToRefElement(triggerElement);
233
+ } else {
234
+ removeAriaFromRefElement(triggerElement);
235
+ }
236
+ };
237
+
238
+ useEffect(() => {
239
+ updateTriggerElementAria(visible);
240
+
241
+ return () => {
242
+ updateTriggerElementAria(false);
243
+ };
244
+ }, [visible, aria, triggerRef, children, id]);
245
+
177
246
  const onDocumentKeyDown = (event: KeyboardEvent) => {
178
247
  if (!triggerManually) {
179
248
  if (event.key === KeyTypes.Escape && visible) {
@@ -258,8 +327,12 @@ export const Tooltip: React.FunctionComponent<TooltipProps> = ({
258
327
  }
259
328
  };
260
329
 
261
- const addAriaToTrigger = () => {
262
- if (aria === 'describedby' && children && children.props && !children.props['aria-describedby']) {
330
+ const addAriaToChildren = () => {
331
+ if (!children) {
332
+ return;
333
+ }
334
+
335
+ if (aria === 'describedby' && children.props && !children.props['aria-describedby']) {
263
336
  return cloneElement(children, { 'aria-describedby': id });
264
337
  } else if (aria === 'labelledby' && children.props && !children.props['aria-labelledby']) {
265
338
  return cloneElement(children, { 'aria-labelledby': id });
@@ -269,7 +342,7 @@ export const Tooltip: React.FunctionComponent<TooltipProps> = ({
269
342
 
270
343
  return (
271
344
  <Popper
272
- trigger={aria !== 'none' && visible ? addAriaToTrigger() : children}
345
+ trigger={aria !== 'none' && visible ? addAriaToChildren() : children}
273
346
  triggerRef={triggerRef}
274
347
  popper={content}
275
348
  popperRef={popperRef}