diff --git a/demo/src/screens/componentScreens/TabControllerScreen/index.tsx b/demo/src/screens/componentScreens/TabControllerScreen/index.tsx
index b302337202..24a8f838b6 100644
--- a/demo/src/screens/componentScreens/TabControllerScreen/index.tsx
+++ b/demo/src/screens/componentScreens/TabControllerScreen/index.tsx
@@ -22,6 +22,7 @@ interface State {
asCarousel: boolean;
centerSelected: boolean;
fewItems: boolean;
+ accessibilityFocusOnSelected: boolean;
initialIndex: number;
selectedIndex: number;
key: string | number;
@@ -35,6 +36,7 @@ class TabControllerScreen extends Component<{}, State> {
asCarousel: true,
centerSelected: false,
fewItems: false,
+ accessibilityFocusOnSelected: false,
initialIndex: 0,
selectedIndex: 0,
key: Date.now(),
@@ -105,6 +107,10 @@ class TabControllerScreen extends Component<{}, State> {
});
};
+ toggleAccessibilityFocusOnSelected = () => {
+ this.setState({accessibilityFocusOnSelected: !this.state.accessibilityFocusOnSelected});
+ };
+
toggleCenterSelected = () => {
const {fewItems, centerSelected} = this.state;
this.setState({
@@ -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 (
{
initialIndex={initialIndex}
onChangeIndex={this.onChangeIndex}
items={items}
+ accessibilityFocusOnSelected={accessibilityFocusOnSelected}
>
{
marginB-12
onPress={this.toggleCenterSelected}
/>
+
diff --git a/packages/react-native-ui-lib/src/components/tabController/TabBarContext.ts b/packages/react-native-ui-lib/src/components/tabController/TabBarContext.ts
index 69a11e6f18..7e9988576b 100644
--- a/packages/react-native-ui-lib/src/components/tabController/TabBarContext.ts
+++ b/packages/react-native-ui-lib/src/components/tabController/TabBarContext.ts
@@ -15,6 +15,7 @@ interface TabControllerContext {
targetPage: Reanimated.SharedValue;
/* carouselOffset: Reanimated.SharedValue; */
setCurrentIndex: (index: number) => void;
+ accessibilityFocusOnSelected?: boolean;
}
// @ts-expect-error
diff --git a/packages/react-native-ui-lib/src/components/tabController/TabBarItem.tsx b/packages/react-native-ui-lib/src/components/tabController/TabBarItem.tsx
index d1075746aa..1f6244c28d 100644
--- a/packages/react-native-ui-lib/src/components/tabController/TabBarItem.tsx
+++ b/packages/react-native-ui-lib/src/components/tabController/TabBarItem.tsx
@@ -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';
@@ -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);
@@ -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;
diff --git a/packages/react-native-ui-lib/src/components/tabController/index.tsx b/packages/react-native-ui-lib/src/components/tabController/index.tsx
index acd2b06ffb..b27cb4ba67 100644
--- a/packages/react-native-ui-lib/src/components/tabController/index.tsx
+++ b/packages/react-native-ui-lib/src/components/tabController/index.tsx
@@ -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;
}
@@ -73,6 +80,7 @@ const TabController = React.forwardRef((props: PropsWithChildren(getScreenWidth(useSafeArea));
@@ -138,9 +146,11 @@ const TabController = React.forwardRef((props: PropsWithChildren{children};
});