Skip to content
Open
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
17 changes: 16 additions & 1 deletion demo/src/screens/componentScreens/TabControllerScreen/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface State {
asCarousel: boolean;
centerSelected: boolean;
fewItems: boolean;
accessibilityFocusOnSelected: boolean;
initialIndex: number;
selectedIndex: number;
key: string | number;
Expand All @@ -35,6 +36,7 @@ class TabControllerScreen extends Component<{}, State> {
asCarousel: true,
centerSelected: false,
fewItems: false,
accessibilityFocusOnSelected: false,
initialIndex: 0,
selectedIndex: 0,
key: Date.now(),
Expand Down Expand Up @@ -105,6 +107,10 @@ class TabControllerScreen extends Component<{}, State> {
});
};

toggleAccessibilityFocusOnSelected = () => {
this.setState({accessibilityFocusOnSelected: !this.state.accessibilityFocusOnSelected});
};

toggleCenterSelected = () => {
const {fewItems, centerSelected} = this.state;
this.setState({
Expand Down Expand Up @@ -160,7 +166,7 @@ class TabControllerScreen extends Component<{}, State> {
}

render() {
const {key, initialIndex, asCarousel, centerSelected, fewItems, items} = this.state;
const {key, initialIndex, asCarousel, centerSelected, fewItems, accessibilityFocusOnSelected, items} = this.state;
return (
<View flex bg-$backgroundDefault>
<TabController
Expand All @@ -170,6 +176,7 @@ class TabControllerScreen extends Component<{}, State> {
initialIndex={initialIndex}
onChangeIndex={this.onChangeIndex}
items={items}
accessibilityFocusOnSelected={accessibilityFocusOnSelected}
>
<TabController.TabBar
// items={items}
Expand Down Expand Up @@ -216,6 +223,14 @@ class TabControllerScreen extends Component<{}, State> {
marginB-12
onPress={this.toggleCenterSelected}
/>
<Button
bg-grey20={!accessibilityFocusOnSelected}
bg-green30={accessibilityFocusOnSelected}
label={`A11y Focus on Select : ${accessibilityFocusOnSelected ? 'ON' : 'OFF'}`}
size={Button.sizes.small}
marginB-12
onPress={this.toggleAccessibilityFocusOnSelected}
/>
<Button label="setTab (Imperative)" bg-green10 onPress={this.setTab} size={Button.sizes.small}/>
</View>
</View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface TabControllerContext {
targetPage: Reanimated.SharedValue<number>;
/* carouselOffset: Reanimated.SharedValue<number>; */
setCurrentIndex: (index: number) => void;
accessibilityFocusOnSelected?: boolean;
}

// @ts-expect-error
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// TODO: support commented props
import React, {useCallback, useContext, useEffect, useRef, useMemo, ReactElement, useState} from 'react';
import {StyleSheet, TextStyle, LayoutChangeEvent, StyleProp, ViewStyle, TextProps} from 'react-native';
import {StyleSheet, TextStyle, LayoutChangeEvent, StyleProp, ViewStyle, TextProps, AccessibilityInfo, findNodeHandle} from 'react-native';
import _ from 'lodash';
import Reanimated, {runOnJS, useAnimatedReaction, useAnimatedStyle, useSharedValue} from 'react-native-reanimated';
import {Gesture, GestureDetector} from 'react-native-gesture-handler';
Expand Down Expand Up @@ -137,7 +137,7 @@ export default function TabBarItem({
onPress,
...props
}: Props) {
const {currentPage, setCurrentIndex} = useContext(TabBarContext);
const {currentPage, setCurrentIndex, accessibilityFocusOnSelected} = useContext(TabBarContext);
const itemRef = useRef();
const itemWidth = useRef(props.width);
const isPressed = useSharedValue(false);
Expand All @@ -164,6 +164,20 @@ export default function TabBarItem({
}
});

const isMounted = useRef(false);
useEffect(() => {
if (!isMounted.current) {
isMounted.current = true;
return;
}
if (accessibilityFocusOnSelected && isSelected) {
const node = findNodeHandle(itemRef.current as unknown as React.Component | null);
if (node) {
AccessibilityInfo.setAccessibilityFocus(node);
}
}
}, [isSelected, accessibilityFocusOnSelected]);

const onLayout = useCallback((event: LayoutChangeEvent) => {
const {width} = event.nativeEvent.layout;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ export interface TabControllerProps {
* Send if a SafeView is used in the context of the TabController.
*/
useSafeArea?: boolean;
/**
* When true, accessibility focus will be moved to the selected tab bar item
* whenever the selected index changes (e.g. via swipe or programmatic change).
* Useful for screen-reader users so the newly selected tab is announced automatically.
* @default false
*/
accessibilityFocusOnSelected?: boolean;
children?: React.ReactNode;
}

Expand All @@ -73,6 +80,7 @@ const TabController = React.forwardRef((props: PropsWithChildren<TabControllerPr
onChangeIndex = _.noop,
carouselPageWidth,
useSafeArea = false,
accessibilityFocusOnSelected = false,
children
} = themeProps;
const [screenWidth, setScreenWidth] = useState<number>(getScreenWidth(useSafeArea));
Expand Down Expand Up @@ -138,9 +146,11 @@ const TabController = React.forwardRef((props: PropsWithChildren<TabControllerPr
containerWidth: screenWidth,
/* Callbacks */
onChangeIndex,
setCurrentIndex
setCurrentIndex,
/* Accessibility */
accessibilityFocusOnSelected
};
}, [initialIndex, asCarousel, items, onChangeIndex, screenWidth, nestedInScrollView]);
}, [initialIndex, asCarousel, items, onChangeIndex, screenWidth, nestedInScrollView, accessibilityFocusOnSelected]);

return <TabBarContext.Provider value={context}>{children}</TabBarContext.Provider>;
});
Expand Down
Loading