Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/react-core/src/components/Page/PageBreadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export interface PageBreadcrumbProps extends React.HTMLProps<HTMLElement> {
hasShadowBottom?: boolean;
/** Flag indicating if the PageBreadcrumb has a scrolling overflow */
hasOverflowScroll?: boolean;
/** Adds an accessible name to the breadcrumb section. Required when the hasOverflowScroll prop is set to true. */
'aria-label'?: string;
}

export const PageBreadcrumb = ({
Expand All @@ -39,10 +41,18 @@ export const PageBreadcrumb = ({
hasShadowTop = false,
hasShadowBottom = false,
hasOverflowScroll = false,
'aria-label': ariaLabel,
...props
}: PageBreadcrumbProps) => {
const { height, getVerticalBreakpoint } = React.useContext(PageContext);

React.useEffect(() => {
if (hasOverflowScroll && !ariaLabel) {
/* eslint-disable no-console */
console.warn('PageBreadcrumb: An accessible aria-label is required when hasOverflowScroll is set to true.');
}
}, [hasOverflowScroll, ariaLabel]);

return (
<section
className={css(
Expand All @@ -57,6 +67,7 @@ export const PageBreadcrumb = ({
className
)}
{...(hasOverflowScroll && { tabIndex: 0 })}
aria-label={ariaLabel}
{...props}
>
{isWidthLimited && <div className={css(styles.pageMainBody)}>{children}</div>}
Expand Down
11 changes: 11 additions & 0 deletions packages/react-core/src/components/Page/PageGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export interface PageGroupProps extends React.HTMLProps<HTMLDivElement> {
hasShadowBottom?: boolean;
/** Flag indicating if the PageGroup has a scrolling overflow */
hasOverflowScroll?: boolean;
/** Adds an accessible name to the page group. Required when the hasOverflowScroll prop is set to true. */
'aria-label'?: string;
}

export const PageGroup = ({
Expand All @@ -35,10 +37,18 @@ export const PageGroup = ({
hasShadowTop = false,
hasShadowBottom = false,
hasOverflowScroll = false,
'aria-label': ariaLabel,
...props
}: PageGroupProps) => {
const { height, getVerticalBreakpoint } = React.useContext(PageContext);

React.useEffect(() => {
if (hasOverflowScroll && !ariaLabel) {
/* eslint-disable no-console */
console.warn('PageGroup: An accessible aria-label is required when hasOverflowScroll is set to true.');
}
}, [hasOverflowScroll, ariaLabel]);

return (
<div
{...props}
Expand All @@ -53,6 +63,7 @@ export const PageGroup = ({
className
)}
{...(hasOverflowScroll && { tabIndex: 0 })}
aria-label={ariaLabel}
>
{children}
</div>
Expand Down
11 changes: 11 additions & 0 deletions packages/react-core/src/components/Page/PageNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export interface PageNavigationProps extends React.HTMLProps<HTMLDivElement> {
hasShadowBottom?: boolean;
/** Flag indicating if the PageNavigation has a scrolling overflow */
hasOverflowScroll?: boolean;
/** Adds an accessible name to the page navigation. Required when the hasOverflowScroll prop is set to true. */
'aria-label'?: string;
}

export const PageNavigation = ({
Expand All @@ -39,10 +41,18 @@ export const PageNavigation = ({
hasShadowTop = false,
hasShadowBottom = false,
hasOverflowScroll = false,
'aria-label': ariaLabel,
...props
}: PageNavigationProps) => {
const { height, getVerticalBreakpoint } = React.useContext(PageContext);

React.useEffect(() => {
if (hasOverflowScroll && !ariaLabel) {
/* eslint-disable no-console */
console.warn('PageNavigation: An accessible aria-label is required when hasOverflowScroll is set to true.');
}
}, [hasOverflowScroll, ariaLabel]);

return (
<div
className={css(
Expand All @@ -57,6 +67,7 @@ export const PageNavigation = ({
className
)}
{...(hasOverflowScroll && { tabIndex: 0 })}
aria-label={ariaLabel}
{...props}
>
{isWidthLimited && <div className={css(styles.pageMainBody)}>{children}</div>}
Expand Down
13 changes: 13 additions & 0 deletions packages/react-core/src/components/Page/PageSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export interface PageSectionProps extends React.HTMLProps<HTMLDivElement> {
hasShadowBottom?: boolean;
/** Flag indicating if the PageSection has a scrolling overflow */
hasOverflowScroll?: boolean;
/** Adds an accessible name to the page section. Required when the hasOverflowScroll prop is set to true.
* This prop should also be passed in if a heading is not being used to describe the content of the page section.
*/
'aria-label'?: string;
}

const variantType = {
Expand Down Expand Up @@ -93,10 +97,18 @@ export const PageSection: React.FunctionComponent<PageSectionProps> = ({
hasShadowTop = false,
hasShadowBottom = false,
hasOverflowScroll = false,
'aria-label': ariaLabel,
...props
}: PageSectionProps) => {
const { height, getVerticalBreakpoint } = React.useContext(PageContext);

React.useEffect(() => {
if (hasOverflowScroll && !ariaLabel) {
/* eslint-disable no-console */
console.warn('PageSection: An accessible aria-label is required when hasOverflowScroll is set to true.');
}
}, [hasOverflowScroll, ariaLabel]);

return (
<section
{...props}
Expand All @@ -117,6 +129,7 @@ export const PageSection: React.FunctionComponent<PageSectionProps> = ({
className
)}
{...(hasOverflowScroll && { tabIndex: 0 })}
aria-label={ariaLabel}
>
{isWidthLimited && <div className={css(styles.pageMainBody)}>{children}</div>}
{!isWidthLimited && children}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import { PageBreadcrumb } from '../PageBreadcrumb';

describe('page breadcrumb', () => {
Expand Down Expand Up @@ -31,4 +31,44 @@ describe('page breadcrumb', () => {
const { asFragment } = render(<PageBreadcrumb hasOverflowScroll>test</PageBreadcrumb>);
expect(asFragment()).toMatchSnapshot();
});

test('Renders without an aria-label by default', () => {
render(<PageBreadcrumb>test</PageBreadcrumb>);

expect(screen.getByText('test')).not.toHaveAccessibleName('Test label');
});

test('Renders with the passed aria-label applied', () => {
render(<PageBreadcrumb aria-label="Test label">test</PageBreadcrumb>);

expect(screen.getByText('test')).toHaveAccessibleName('Test label');
});

test('Does not log a warning in the console by default', () => {
const consoleWarning = jest.spyOn(console, 'warn').mockImplementation();

render(<PageBreadcrumb>test</PageBreadcrumb>);

expect(consoleWarning).not.toHaveBeenCalled();
});

test('Does not log a warning in the console when an aria-label is included with hasOverflowScroll', () => {
const consoleWarning = jest.spyOn(console, 'warn').mockImplementation();

render(
<PageBreadcrumb hasOverflowScroll aria-label="Test label">
test
</PageBreadcrumb>
);

expect(consoleWarning).not.toHaveBeenCalled();
});

test('Logs a warning in the console when an aria-label is not included with hasOverflowScroll', () => {
const consoleWarning = jest.spyOn(console, 'warn').mockImplementation();

render(<PageBreadcrumb hasOverflowScroll>test</PageBreadcrumb>);

expect(consoleWarning).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import { PageGroup } from '../PageGroup';

describe('page group', () => {
Expand Down Expand Up @@ -27,4 +27,44 @@ describe('page group', () => {
const { asFragment } = render(<PageGroup hasOverflowScroll>test</PageGroup>);
expect(asFragment()).toMatchSnapshot();
});

test('Renders without an aria-label by default', () => {
render(<PageGroup>test</PageGroup>);

expect(screen.getByText('test')).not.toHaveAccessibleName('Test label');
});

test('Renders with the passed aria-label applied', () => {
render(<PageGroup aria-label="Test label">test</PageGroup>);

expect(screen.getByText('test')).toHaveAccessibleName('Test label');
});

test('Does not log a warning in the console by default', () => {
const consoleWarning = jest.spyOn(console, 'warn').mockImplementation();

render(<PageGroup>test</PageGroup>);

expect(consoleWarning).not.toHaveBeenCalled();
});

test('Does not log a warning in the console when an aria-label is included with hasOverflowScroll', () => {
const consoleWarning = jest.spyOn(console, 'warn').mockImplementation();

render(
<PageGroup hasOverflowScroll aria-label="Test label">
test
</PageGroup>
);

expect(consoleWarning).not.toHaveBeenCalled();
});

test('Logs a warning in the console when an aria-label is not included with hasOverflowScroll', () => {
const consoleWarning = jest.spyOn(console, 'warn').mockImplementation();

render(<PageGroup hasOverflowScroll>test</PageGroup>);

expect(consoleWarning).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import { PageNavigation } from '../PageNavigation';

describe('page navigation', () => {
Expand Down Expand Up @@ -31,4 +31,44 @@ describe('page navigation', () => {
const { asFragment } = render(<PageNavigation hasOverflowScroll>test</PageNavigation>);
expect(asFragment()).toMatchSnapshot();
});

test('Renders without an aria-label by default', () => {
render(<PageNavigation>test</PageNavigation>);

expect(screen.getByText('test')).not.toHaveAccessibleName('Test label');
});

test('Renders with the passed aria-label applied', () => {
render(<PageNavigation aria-label="Test label">test</PageNavigation>);

expect(screen.getByText('test')).toHaveAccessibleName('Test label');
});

test('Does not log a warning in the console by default', () => {
const consoleWarning = jest.spyOn(console, 'warn').mockImplementation();

render(<PageNavigation>test</PageNavigation>);

expect(consoleWarning).not.toHaveBeenCalled();
});

test('Does not log a warning in the console when an aria-label is included with hasOverflowScroll', () => {
const consoleWarning = jest.spyOn(console, 'warn').mockImplementation();

render(
<PageNavigation hasOverflowScroll aria-label="Test label">
test
</PageNavigation>
);

expect(consoleWarning).not.toHaveBeenCalled();
});

test('Logs a warning in the console when an aria-label is not included with hasOverflowScroll', () => {
const consoleWarning = jest.spyOn(console, 'warn').mockImplementation();

render(<PageNavigation hasOverflowScroll>test</PageNavigation>);

expect(consoleWarning).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import { PageSection, PageSectionTypes } from '../PageSection';

jest.mock('../Page');
Expand Down Expand Up @@ -88,3 +88,43 @@ test('Verify page section overflow scroll', () => {
const { asFragment } = render(<PageSection hasOverflowScroll>test</PageSection>);
expect(asFragment()).toMatchSnapshot();
});

test('Renders without an aria-label by default', () => {
render(<PageSection>test</PageSection>);

expect(screen.getByText('test')).not.toHaveAccessibleName('Test label');
});

test('Renders with the passed aria-label applied', () => {
render(<PageSection aria-label="Test label">test</PageSection>);

expect(screen.getByText('test')).toHaveAccessibleName('Test label');
});

test('Does not log a warning in the console by default', () => {
const consoleWarning = jest.spyOn(console, 'warn').mockImplementation();

render(<PageSection>test</PageSection>);

expect(consoleWarning).not.toHaveBeenCalled();
});

test('Does not log a warning in the console when an aria-label is included with hasOverflowScroll', () => {
const consoleWarning = jest.spyOn(console, 'warn').mockImplementation();

render(
<PageSection hasOverflowScroll aria-label="Test label">
test
</PageSection>
);

expect(consoleWarning).not.toHaveBeenCalled();
});

test('Logs a warning in the console when an aria-label is not included with hasOverflowScroll', () => {
const consoleWarning = jest.spyOn(console, 'warn').mockImplementation();

render(<PageSection hasOverflowScroll>test</PageSection>);

expect(consoleWarning).toHaveBeenCalled();
});
1 change: 1 addition & 0 deletions packages/react-core/src/demos/JumpLinks.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import DashboardWrapper from './examples/DashboardWrapper';
JumpLinks has a scrollspy built-in to make your implementation easier. When implementing JumpLinks be sure to:

1. Find the correct `scrollableSelector` for your page via [Firefox's debugging scrollable overflow](https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Debug_Scrollable_Overflow) or by adding `hasOverflowScroll` to a [PageSection](/components/page#pagesection) or [PageGroup](/components/page#pagegroup).
- If you add `hasOverflowScroll` to a Page sub-component you should also add a relevant aria-label to that component as well.
2. Provide `href`s to your JumpLinksItems which match the `id` of elements you want to spy on. If you wish to scroll to a different item than you're linking to use the `node` prop.

### Scrollspy with subsections
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ export const Name = () => {
<Switch label="Always show BackToTopButton" onChange={handleChange} isChecked={isAlwaysVisible} />
</TextContent>
</PageSection>
<PageSection hasOverflowScroll name="scrolling-section" tabIndex={0}>
<PageSection
hasOverflowScroll
name="scrolling-section"
tabIndex={0}
aria-label="Scrollable container of demonstration cards with a back to top element"
>
<Gallery hasGutter>
{Array.apply(0, Array(60)).map((_x: any, i: number) => (
<GalleryItem key={i}>
Expand Down