diff --git a/packages/ui-breadcrumb/src/Breadcrumb/BreadcrumbLink/index.tsx b/packages/ui-breadcrumb/src/Breadcrumb/BreadcrumbLink/index.tsx index 54fb5aa866..d46cf49793 100644 --- a/packages/ui-breadcrumb/src/Breadcrumb/BreadcrumbLink/index.tsx +++ b/packages/ui-breadcrumb/src/Breadcrumb/BreadcrumbLink/index.tsx @@ -54,8 +54,15 @@ class BreadcrumbLink extends Component { } render() { - const { children, href, renderIcon, iconPlacement, onClick, onMouseEnter } = - this.props + const { + children, + href, + renderIcon, + iconPlacement, + onClick, + onMouseEnter, + isCurrentPage + } = this.props const props = omitProps(this.props, BreadcrumbLink.allowedProps) @@ -69,6 +76,7 @@ class BreadcrumbLink extends Component { onMouseEnter={onMouseEnter} isWithinText={false} elementRef={this.handleRef} + {...(isCurrentPage && { 'aria-current': 'page' })} > {children} diff --git a/packages/ui-breadcrumb/src/Breadcrumb/BreadcrumbLink/props.ts b/packages/ui-breadcrumb/src/Breadcrumb/BreadcrumbLink/props.ts index cca8fd0e45..f981ea6aa4 100644 --- a/packages/ui-breadcrumb/src/Breadcrumb/BreadcrumbLink/props.ts +++ b/packages/ui-breadcrumb/src/Breadcrumb/BreadcrumbLink/props.ts @@ -63,6 +63,11 @@ type BreadcrumbLinkOwnProps = { * Place the icon before or after the text in the Breadcrumb.Link */ iconPlacement?: 'start' | 'end' + /** + * Whether the page this breadcrumb points to is the current one. If true, it sets aria-current="page". + * If this prop is not set to true on any breadcrumb, the element recieving the aria-current="page" will always be the last element on the right by default, unless the last last element's isCurrentPage prop is explicity set to false too. + */ + isCurrentPage?: boolean } type PropKeys = keyof BreadcrumbLinkOwnProps @@ -89,7 +94,8 @@ const propTypes: PropValidators = { onMouseEnter: PropTypes.func, size: PropTypes.oneOf(['small', 'medium', 'large']), renderIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), - iconPlacement: PropTypes.oneOf(['start', 'end']) + iconPlacement: PropTypes.oneOf(['start', 'end']), + isCurrentPage: PropTypes.bool } const allowedProps: AllowedPropKeys = [ @@ -99,7 +105,8 @@ const allowedProps: AllowedPropKeys = [ 'onClick', 'onMouseEnter', 'renderIcon', - 'size' + 'size', + 'isCurrentPage' ] export type { BreadcrumbLinkProps } diff --git a/packages/ui-breadcrumb/src/Breadcrumb/README.md b/packages/ui-breadcrumb/src/Breadcrumb/README.md index 7f39e1bd8c..2ff80bed13 100644 --- a/packages/ui-breadcrumb/src/Breadcrumb/README.md +++ b/packages/ui-breadcrumb/src/Breadcrumb/README.md @@ -23,7 +23,7 @@ type: example {(props, matches) => { if (matches.includes('tablet')) { return ( - + Student Forecast University of Utah University of Utah Colleges @@ -52,7 +52,7 @@ Change the `size` prop to control the font-size of the breadcrumbs (default is ` type: example ---
- + English 204 Rabbit Is Rich - + English 204 Rabbit Is Rich - + English 204 ', () => { expect(icon).toBeInTheDocument() expect(icon).toHaveAttribute('aria-hidden', 'true') }) + + it('should add aria-current="page" to the last element by default', () => { + const { container } = render( + + {TEST_TEXT_01} + {TEST_TEXT_02} + + ) + const links = container.querySelectorAll('[class$="--block-link"]') + const firstLink = links[0] + const lastLink = links[links.length - 1] + + expect(firstLink).not.toHaveAttribute('aria-current', 'page') + expect(lastLink).toHaveAttribute('aria-current', 'page') + }) + + it('should add aria-current="page" to the element if isCurrent is true', () => { + const { container } = render( + + + {TEST_TEXT_01} + + {TEST_TEXT_02} + + ) + const links = container.querySelectorAll('[class$="--block-link"]') + const firstLink = links[0] + const lastLink = links[links.length - 1] + + expect(firstLink).toHaveAttribute('aria-current', 'page') + expect(lastLink).not.toHaveAttribute('aria-current', 'page') + }) + + it('should throw a warning when multiple elements have isCurrent set to true ', () => { + render( + + + {TEST_TEXT_01} + + {TEST_TEXT_02} + + ) + + expect(consoleWarningMock).toHaveBeenCalledWith( + expect.stringContaining( + 'Warning: Multiple elements with isCurrentPage=true found. Only one element should be set to current.' + ) + ) + }) + + it('should not add aria-current="page" to the last element if it set to false', () => { + const { container } = render( + + {TEST_TEXT_01} + {TEST_TEXT_02} + + ) + const links = container.querySelectorAll('[class$="--block-link"]') + const firstLink = links[0] + const lastLink = links[links.length - 1] + + expect(firstLink).not.toHaveAttribute('aria-current', 'page') + expect(lastLink).not.toHaveAttribute('aria-current', 'page') + }) }) diff --git a/packages/ui-breadcrumb/src/Breadcrumb/index.tsx b/packages/ui-breadcrumb/src/Breadcrumb/index.tsx index 03f12dc385..ae2509f8ca 100644 --- a/packages/ui-breadcrumb/src/Breadcrumb/index.tsx +++ b/packages/ui-breadcrumb/src/Breadcrumb/index.tsx @@ -62,6 +62,16 @@ class Breadcrumb extends Component { this.ref = el } + addAriaCurrent = (child: React.ReactNode) => { + const updatedChild = React.cloneElement( + child as React.ReactElement<{ 'aria-current'?: string }>, + { + 'aria-current': 'page' + } + ) + return updatedChild + } + componentDidMount() { this.props.makeStyles?.() } @@ -77,10 +87,28 @@ class Breadcrumb extends Component { const inlineStyle = { maxWidth: `${Math.floor(100 / numChildren)}%` } + let isAriaCurrentSet = false + return React.Children.map(children, (child, index) => { + const isLastElement = index === numChildren - 1 + if (React.isValidElement(child)) { + const isCurrentPage = child.props.isCurrentPage || false + if (isAriaCurrentSet && isCurrentPage) { + console.warn( + `Warning: Multiple elements with isCurrentPage=true found. Only one element should be set to current.` + ) + } + if (isCurrentPage) { + isAriaCurrentSet = true + } + } return (
  • - {child} + {!isAriaCurrentSet && + isLastElement && + (child as React.ReactElement).props.isCurrentPage !== false + ? this.addAriaCurrent(child) + : child} {index < numChildren - 1 && ( )}