Skip to content

Commit b605fb8

Browse files
authored
feat: Improvements to Android accessibility (#168)
1 parent b95117a commit b605fb8

File tree

4 files changed

+59
-7
lines changed

4 files changed

+59
-7
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ The below props allow modification of the Android ActionSheet. They have no effe
117117
| textStyle | TextStyle | No | |
118118
| titleTextStyle | TextStyle | No | |
119119
| messageTextStyle | TextStyle | No | |
120+
| autoFocus | boolean | No | false |
120121
| showSeparators | boolean | No | false |
121122
| containerStyle | ViewStyle | No | |
122123
| separatorStyle | ViewStyle | No | |
@@ -138,6 +139,10 @@ Apply any text style props to the title if present.
138139
#### `messageTextStyle` (optional)
139140
Apply any text style props to the message if present.
140141

142+
#### `autoFocus`: (optional)
143+
If true, will give the first option screen reader focus automatically when the action sheet becomes visible.
144+
On iOS, this is the default behavior of the native action sheet.
145+
141146
#### `showSeparators`: (optional)
142147
Show separators between items. On iOS, separators always show so this prop has no effect.
143148

src/ActionSheet/ActionGroup.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import * as React from 'react';
2-
import { StyleSheet, Text, Image, View, ScrollView } from 'react-native';
2+
import {
3+
StyleSheet,
4+
Text,
5+
Image,
6+
View,
7+
ScrollView,
8+
findNodeHandle,
9+
AccessibilityInfo,
10+
Platform,
11+
UIManager,
12+
} from 'react-native';
313
import TouchableNativeFeedbackSafe from './TouchableNativeFeedbackSafe';
414
import { ActionSheetOptions } from '../types';
515

@@ -14,6 +24,28 @@ const BLACK_54PC_TRANSPARENT = '#0000008a';
1424
const BLACK_87PC_TRANSPARENT = '#000000de';
1525
const DESTRUCTIVE_COLOR = '#d32f2f';
1626

27+
/**
28+
* Can be used as a React ref for a component to auto-focus for accessibility on render.
29+
* @param ref The component to auto-focus
30+
*/
31+
const focusViewOnRender = (ref: React.Component | null) => {
32+
if (ref) {
33+
const reactTag = findNodeHandle(ref);
34+
if (reactTag) {
35+
if (Platform.OS === 'android') {
36+
// @ts-ignore: sendAccessibilityEvent is missing from @types/react-native
37+
UIManager.sendAccessibilityEvent(
38+
reactTag,
39+
// @ts-ignore: AccessibilityEventTypes is missing from @types/react-native
40+
UIManager.AccessibilityEventTypes.typeViewFocused
41+
);
42+
} else {
43+
AccessibilityInfo.setAccessibilityFocus(reactTag);
44+
}
45+
}
46+
}
47+
};
48+
1749
export default class ActionGroup extends React.Component<Props> {
1850
static defaultProps = {
1951
title: null,
@@ -80,6 +112,7 @@ export default class ActionGroup extends React.Component<Props> {
80112
length,
81113
textStyle,
82114
tintColor,
115+
autoFocus,
83116
showSeparators,
84117
} = this.props;
85118
const optionViews: React.ReactNode[] = [];
@@ -97,11 +130,13 @@ export default class ActionGroup extends React.Component<Props> {
97130

98131
optionViews.push(
99132
<TouchableNativeFeedbackSafe
133+
ref={autoFocus && i === 0 ? focusViewOnRender : undefined}
100134
key={i}
101135
pressInDelay={0}
102136
background={nativeFeedbackBackground}
103137
onPress={() => onSelect(i)}
104138
style={styles.button}
139+
accessibilityRole="button"
105140
accessibilityLabel={options[i]}>
106141
{this._renderIconElement(iconSource, color)}
107142
<Text style={[styles.text, textStyle, { color }]}>{options[i]}</Text>

src/ActionSheet/index.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,19 @@ export default class ActionSheet extends React.Component<Props, State> {
6666
]}
6767
/>
6868
) : null;
69-
return (
69+
70+
// While the sheet is visible, hide the rest of the app's content from screen readers.
71+
const appContent = (
7072
<View
71-
pointerEvents={this.props.pointerEvents}
72-
style={{
73-
flex: 1,
74-
}}>
73+
style={styles.flexContainer}
74+
importantForAccessibility={isVisible ? 'no-hide-descendants' : 'auto'}>
7575
{React.Children.only(this.props.children)}
76+
</View>
77+
);
78+
79+
return (
80+
<View pointerEvents={this.props.pointerEvents} style={styles.flexContainer}>
81+
{appContent}
7682
{isVisible && !useModal && (
7783
<React.Fragment>
7884
{overlay}
@@ -107,12 +113,13 @@ export default class ActionSheet extends React.Component<Props, State> {
107113
titleTextStyle,
108114
message,
109115
messageTextStyle,
116+
autoFocus,
110117
showSeparators,
111118
containerStyle,
112119
separatorStyle,
113120
} = options;
114121
return (
115-
<TouchableWithoutFeedback onPress={this._selectCancelButton}>
122+
<TouchableWithoutFeedback importantForAccessibility="yes" onPress={this._selectCancelButton}>
116123
<Animated.View
117124
needsOffscreenAlphaCompositing={isAnimating}
118125
style={[
@@ -144,6 +151,7 @@ export default class ActionSheet extends React.Component<Props, State> {
144151
titleTextStyle={titleTextStyle}
145152
message={message || undefined}
146153
messageTextStyle={messageTextStyle}
154+
autoFocus={autoFocus}
147155
showSeparators={showSeparators}
148156
containerStyle={containerStyle}
149157
separatorStyle={separatorStyle}
@@ -264,6 +272,9 @@ export default class ActionSheet extends React.Component<Props, State> {
264272
}
265273

266274
const styles = StyleSheet.create({
275+
flexContainer: {
276+
flex: 1,
277+
},
267278
overlay: {
268279
position: 'absolute',
269280
top: 0,

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface ActionSheetOptions extends ActionSheetIOSOptions {
2323
textStyle?: TextStyle;
2424
titleTextStyle?: TextStyle;
2525
messageTextStyle?: TextStyle;
26+
autoFocus?: boolean;
2627
showSeparators?: boolean;
2728
containerStyle?: ViewStyle;
2829
separatorStyle?: ViewStyle;

0 commit comments

Comments
 (0)