Skip to content

Commit d440ccd

Browse files
committed
fix(ui-breadcrumb): add and update aria tags in Breadcrumb and in documentation
Closes: INSTUI-4268
1 parent 295bcb6 commit d440ccd

File tree

5 files changed

+116
-9
lines changed

5 files changed

+116
-9
lines changed

packages/ui-breadcrumb/src/Breadcrumb/BreadcrumbLink/index.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,15 @@ class BreadcrumbLink extends Component<BreadcrumbLinkProps> {
5454
}
5555

5656
render() {
57-
const { children, href, renderIcon, iconPlacement, onClick, onMouseEnter } =
58-
this.props
57+
const {
58+
children,
59+
href,
60+
renderIcon,
61+
iconPlacement,
62+
onClick,
63+
onMouseEnter,
64+
isCurrentPage
65+
} = this.props
5966

6067
const props = omitProps(this.props, BreadcrumbLink.allowedProps)
6168

@@ -69,6 +76,7 @@ class BreadcrumbLink extends Component<BreadcrumbLinkProps> {
6976
onMouseEnter={onMouseEnter}
7077
isWithinText={false}
7178
elementRef={this.handleRef}
79+
{...(isCurrentPage && { 'aria-current': 'page' })}
7280
>
7381
<TruncateText>{children}</TruncateText>
7482
</Link>

packages/ui-breadcrumb/src/Breadcrumb/BreadcrumbLink/props.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ type BreadcrumbLinkOwnProps = {
6363
* Place the icon before or after the text in the Breadcrumb.Link
6464
*/
6565
iconPlacement?: 'start' | 'end'
66+
/**
67+
* Whether the page this breadcrumb points to is the current one. If true, it sets aria-current="page".
68+
* 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.
69+
*/
70+
isCurrentPage?: boolean
6671
}
6772

6873
type PropKeys = keyof BreadcrumbLinkOwnProps
@@ -89,7 +94,8 @@ const propTypes: PropValidators<PropKeys> = {
8994
onMouseEnter: PropTypes.func,
9095
size: PropTypes.oneOf(['small', 'medium', 'large']),
9196
renderIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
92-
iconPlacement: PropTypes.oneOf(['start', 'end'])
97+
iconPlacement: PropTypes.oneOf(['start', 'end']),
98+
isCurrentPage: PropTypes.bool
9399
}
94100

95101
const allowedProps: AllowedPropKeys = [
@@ -99,7 +105,8 @@ const allowedProps: AllowedPropKeys = [
99105
'onClick',
100106
'onMouseEnter',
101107
'renderIcon',
102-
'size'
108+
'size',
109+
'isCurrentPage'
103110
]
104111

105112
export type { BreadcrumbLinkProps }

packages/ui-breadcrumb/src/Breadcrumb/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type: example
2323
{(props, matches) => {
2424
if (matches.includes('tablet')) {
2525
return (
26-
<Breadcrumb label="You are here:">
26+
<Breadcrumb label="breadcrumb">
2727
<Breadcrumb.Link href="#">Student Forecast</Breadcrumb.Link>
2828
<Breadcrumb.Link href="#">University of Utah</Breadcrumb.Link>
2929
<Breadcrumb.Link href="#">University of Utah Colleges</Breadcrumb.Link>
@@ -52,7 +52,7 @@ Change the `size` prop to control the font-size of the breadcrumbs (default is `
5252
type: example
5353
---
5454
<div>
55-
<Breadcrumb size="small" label="You are here:" margin="none none medium">
55+
<Breadcrumb size="small" label="breadcrumb" margin="none none medium">
5656
<Breadcrumb.Link href="https://instructure.github.io/instructure-ui/">English 204</Breadcrumb.Link>
5757
<Breadcrumb.Link
5858
onClick={function () {
@@ -65,7 +65,7 @@ type: example
6565
<Breadcrumb.Link>Rabbit Is Rich</Breadcrumb.Link>
6666
</Breadcrumb>
6767
<View as="div" width="40rem">
68-
<Breadcrumb label="You are here:" margin="none none medium">
68+
<Breadcrumb label="breadcrumb" margin="none none medium">
6969
<Breadcrumb.Link href="https://instructure.github.io/instructure-ui/">English 204</Breadcrumb.Link>
7070
<Breadcrumb.Link
7171
onClick={function () {
@@ -78,7 +78,7 @@ type: example
7878
<Breadcrumb.Link>Rabbit Is Rich</Breadcrumb.Link>
7979
</Breadcrumb>
8080
</View>
81-
<Breadcrumb size="large" label="You are here:">
81+
<Breadcrumb size="large" label="breadcrumb">
8282
<Breadcrumb.Link href="https://instructure.github.io/instructure-ui/">English 204</Breadcrumb.Link>
8383
<Breadcrumb.Link
8484
onClick={function () {

packages/ui-breadcrumb/src/Breadcrumb/__new-tests__/Breadcrumb.test.tsx

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,68 @@ describe('<Breadcrumb />', () => {
111111
expect(icon).toBeInTheDocument()
112112
expect(icon).toHaveAttribute('aria-hidden', 'true')
113113
})
114+
115+
it('should add aria-current="page" to the last element by default', () => {
116+
const { container } = render(
117+
<Breadcrumb label={TEST_LABEL}>
118+
<Breadcrumb.Link href={TEST_LINK}>{TEST_TEXT_01}</Breadcrumb.Link>
119+
<Breadcrumb.Link>{TEST_TEXT_02}</Breadcrumb.Link>
120+
</Breadcrumb>
121+
)
122+
const links = container.querySelectorAll('[class$="--block-link"]')
123+
const firstLink = links[0]
124+
const lastLink = links[links.length - 1]
125+
126+
expect(firstLink).not.toHaveAttribute('aria-current', 'page')
127+
expect(lastLink).toHaveAttribute('aria-current', 'page')
128+
})
129+
130+
it('should add aria-current="page" to the element if isCurrent is true', () => {
131+
const { container } = render(
132+
<Breadcrumb label={TEST_LABEL}>
133+
<Breadcrumb.Link isCurrentPage href={TEST_LINK}>
134+
{TEST_TEXT_01}
135+
</Breadcrumb.Link>
136+
<Breadcrumb.Link>{TEST_TEXT_02}</Breadcrumb.Link>
137+
</Breadcrumb>
138+
)
139+
const links = container.querySelectorAll('[class$="--block-link"]')
140+
const firstLink = links[0]
141+
const lastLink = links[links.length - 1]
142+
143+
expect(firstLink).toHaveAttribute('aria-current', 'page')
144+
expect(lastLink).not.toHaveAttribute('aria-current', 'page')
145+
})
146+
147+
it('should throw a warning when multiple elements have isCurrent set to true ', () => {
148+
render(
149+
<Breadcrumb label={TEST_LABEL}>
150+
<Breadcrumb.Link isCurrentPage href={TEST_LINK}>
151+
{TEST_TEXT_01}
152+
</Breadcrumb.Link>
153+
<Breadcrumb.Link isCurrentPage>{TEST_TEXT_02}</Breadcrumb.Link>
154+
</Breadcrumb>
155+
)
156+
157+
expect(consoleWarningMock).toHaveBeenCalledWith(
158+
expect.stringContaining(
159+
'Warning: Multiple elements with isCurrentPage=true found. Only one element should be set to current.'
160+
)
161+
)
162+
})
163+
164+
it('should not add aria-current="page" to the last element if it set to false', () => {
165+
const { container } = render(
166+
<Breadcrumb label={TEST_LABEL}>
167+
<Breadcrumb.Link href={TEST_LINK}>{TEST_TEXT_01}</Breadcrumb.Link>
168+
<Breadcrumb.Link isCurrentPage={false}>{TEST_TEXT_02}</Breadcrumb.Link>
169+
</Breadcrumb>
170+
)
171+
const links = container.querySelectorAll('[class$="--block-link"]')
172+
const firstLink = links[0]
173+
const lastLink = links[links.length - 1]
174+
175+
expect(firstLink).not.toHaveAttribute('aria-current', 'page')
176+
expect(lastLink).not.toHaveAttribute('aria-current', 'page')
177+
})
114178
})

packages/ui-breadcrumb/src/Breadcrumb/index.tsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,16 @@ class Breadcrumb extends Component<BreadcrumbProps> {
6262
this.ref = el
6363
}
6464

65+
addAriaCurrent = (child: React.ReactNode) => {
66+
const updatedChild = React.cloneElement(
67+
child as React.ReactElement<{ 'aria-current'?: string }>,
68+
{
69+
'aria-current': 'page'
70+
}
71+
)
72+
return updatedChild
73+
}
74+
6575
componentDidMount() {
6676
this.props.makeStyles?.()
6777
}
@@ -77,10 +87,28 @@ class Breadcrumb extends Component<BreadcrumbProps> {
7787
const inlineStyle = {
7888
maxWidth: `${Math.floor(100 / numChildren)}%`
7989
}
90+
let isAriaCurrentSet = false
91+
8092
return React.Children.map(children, (child, index) => {
93+
const isLastElement = index === numChildren - 1
94+
if (React.isValidElement(child)) {
95+
const isCurrentPage = child.props.isCurrentPage || false
96+
if (isAriaCurrentSet && isCurrentPage) {
97+
console.warn(
98+
`Warning: Multiple elements with isCurrentPage=true found. Only one element should be set to current.`
99+
)
100+
}
101+
if (isCurrentPage) {
102+
isAriaCurrentSet = true
103+
}
104+
}
81105
return (
82106
<li css={styles?.crumb} style={inlineStyle}>
83-
{child}
107+
{!isAriaCurrentSet &&
108+
isLastElement &&
109+
(child as React.ReactElement).props.isCurrentPage !== false
110+
? this.addAriaCurrent(child)
111+
: child}
84112
{index < numChildren - 1 && (
85113
<IconArrowOpenEndSolid color="auto" css={styles?.separator} />
86114
)}

0 commit comments

Comments
 (0)