Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.

Commit 35aaf02

Browse files
fix(Loader): Adding labeling when loader get focus (#2352)
* adding labelling when loader get focus * Update docs/src/examples/components/Loader/BestPractices/LoaderBestPractices.tsx Co-Authored-By: Juraj Kapsiar <jurokapsiar@gmail.com> * Update packages/react/src/components/Loader/Loader.tsx Co-Authored-By: Juraj Kapsiar <jurokapsiar@gmail.com> * changes after review * prettier applied * changelog update * reducing the code, apply fix from review * lint fix * fix unit test Co-authored-by: Juraj Kapsiar <jurokapsiar@gmail.com>
1 parent 112a841 commit 35aaf02

File tree

5 files changed

+89
-8
lines changed

5 files changed

+89
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
3232
- Fix `input` descenders being cropped in the Teams theme @bcalvery ([#2335](https://github.com/microsoft/fluent-ui-react/pull/2335))
3333
- Use referentially equal objects for `actions` in `useStateManager` @layershifter ([#2347](https://github.com/microsoft/fluent-ui-react/pull/2347))
3434
- Fix `Animation` component not to throw when `children` is not provided @mnajdova ([#2345](https://github.com/microsoft/fluent-ui-react/pull/2345))
35+
- Fix `loader` - adding labeling when loader get focus @kolaps33 ([#2352](https://github.com/microsoft/fluent-ui-react/pull/2352))
3536

3637
### Features
3738
- Added sourcemaps to the dist output to simplify debugging @miroslavstastny ([#2329](https://github.com/microsoft/fluent-ui-react/pull/2329))
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as React from 'react'
2+
3+
import ComponentBestPractices from '../../../../components/ComponentBestPractices'
4+
5+
const doList = [
6+
'Use react-aria-live or similar component to announce the loading state.',
7+
'If loader is only the element in the screen or region, consider making it focusable by setting `tabIndex` prop to the `Loader`. In most of these cases value of `tabIndex` should be 0.',
8+
]
9+
10+
const LoaderBestPractices: React.FunctionComponent<{}> = () => {
11+
return <ComponentBestPractices doList={doList} />
12+
}
13+
14+
export default LoaderBestPractices

packages/accessibility/src/behaviors/Loader/loaderBehavior.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,37 @@ import { Accessibility } from '../../types'
33
/**
44
* @description
55
* Loader is usually an element that displays the progress status for a task that take a long time or consists of several steps.
6+
* Adds attribute 'aria-labelledby' on 'root' when loader has 'tabIndex' prop. This can be overriden by providing 'aria-labelledby' or 'aria-label' property directly to the component.
67
*
78
* @specification
89
* Adds role 'progressbar' to 'root' slot.
910
*/
1011

11-
const loaderBehavior: Accessibility = () => ({
12-
attributes: {
13-
root: {
14-
role: 'progressbar',
12+
const loaderBehavior: Accessibility<LoaderBehaviorProps> = props => {
13+
return {
14+
attributes: {
15+
root: {
16+
role: 'progressbar',
17+
'aria-labelledby': getDefaultAriaLabelledBy(props),
18+
},
1519
},
16-
},
17-
})
20+
}
21+
}
22+
23+
type LoaderBehaviorProps = {
24+
/** id of the loader label element. */
25+
labelId?: string
26+
}
1827

1928
export default loaderBehavior
29+
30+
/**
31+
* Returns the id of the loader label if user provide tabIndex prop. It is used when user does not provide aria-label or
32+
* aria-labelledby as prop.
33+
*/
34+
const getDefaultAriaLabelledBy = (props: LoaderBehaviorProps) => {
35+
if (props['aria-label'] || props['aria-labelledby']) {
36+
return undefined
37+
}
38+
return props['tabIndex'] === undefined ? undefined : props.labelId
39+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { loaderBehavior } from '@fluentui/accessibility'
2+
3+
describe('LoaderBehavior.ts', () => {
4+
test('do NOT add aria-labelledby, when aria-label was set already', () => {
5+
const props = { labelId: 'label-id', 'aria-label': 'any loading string' }
6+
const expectedResult = loaderBehavior(props)
7+
expect(expectedResult.attributes.root['aria-labelledby']).toEqual(undefined)
8+
})
9+
10+
test('do NOT add aria-labelledby, when aria-labelled was set already', () => {
11+
const props = { labelId: 'label-id', 'aria-labelledby': 'id' }
12+
const expectedResult = loaderBehavior(props)
13+
expect(expectedResult.attributes.root['aria-labelledby']).toEqual(undefined)
14+
})
15+
16+
test('do NOT add aria-labelledby, when there is no tabIndex specified', () => {
17+
const props = { labelId: 'label-id' }
18+
const expectedResult = loaderBehavior(props)
19+
expect(expectedResult.attributes.root['aria-labelledby']).toEqual(undefined)
20+
})
21+
22+
test('add aria-labelledby, when there is tabIndex=0 specified', () => {
23+
const props = { labelId: 'label-id', tabIndex: 0 }
24+
const expectedResult = loaderBehavior(props)
25+
expect(expectedResult.attributes.root['aria-labelledby']).toEqual('label-id')
26+
})
27+
28+
test('add aria-labelledby, when there is tabIndex=-1 specified', () => {
29+
const props = { labelId: 'label-id', tabIndex: -1 }
30+
const expectedResult = loaderBehavior(props)
31+
expect(expectedResult.attributes.root['aria-labelledby']).toEqual('label-id')
32+
})
33+
})

packages/react/src/components/Loader/Loader.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
commonPropTypes,
1111
SizeValue,
1212
ShorthandFactory,
13+
getOrGenerateIdFromShorthand,
1314
} from '../../utils'
1415
import { WithAsProp, ShorthandValue, withSafeTypeForAs } from '../../types'
1516
import Box, { BoxProps } from '../Box/Box'
@@ -49,6 +50,7 @@ export interface LoaderProps extends UIComponentProps {
4950

5051
export interface LoaderState {
5152
visible: boolean
53+
labelId: string
5254
}
5355

5456
/**
@@ -94,6 +96,13 @@ class Loader extends UIComponent<WithAsProp<LoaderProps>, LoaderState> {
9496

9597
this.state = {
9698
visible: this.props.delay === 0,
99+
labelId: '',
100+
}
101+
}
102+
103+
static getDerivedStateFromProps(props, state) {
104+
return {
105+
labelId: getOrGenerateIdFromShorthand('loader-label-', props.label, state.labelId),
97106
}
98107
}
99108

@@ -114,7 +123,7 @@ class Loader extends UIComponent<WithAsProp<LoaderProps>, LoaderState> {
114123

115124
renderComponent({ ElementType, classes, accessibility, variables, styles, unhandledProps }) {
116125
const { indicator, label, svg } = this.props
117-
const { visible } = this.state
126+
const { visible, labelId } = this.state
118127

119128
const svgElement = Box.create(svg, {
120129
defaultProps: () => ({ className: Loader.slotClassNames.svg, styles: styles.svg }),
@@ -135,7 +144,11 @@ class Loader extends UIComponent<WithAsProp<LoaderProps>, LoaderState> {
135144
}),
136145
})}
137146
{Text.create(label, {
138-
defaultProps: () => ({ className: Loader.slotClassNames.label, styles: styles.label }),
147+
defaultProps: () => ({
148+
className: Loader.slotClassNames.label,
149+
styles: styles.label,
150+
id: labelId,
151+
}),
139152
})}
140153
</ElementType>
141154
)

0 commit comments

Comments
 (0)