From dadc54784de23add6261187349d3f400fe4c4750 Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Sun, 25 Feb 2024 01:51:52 +0100 Subject: [PATCH] refactor: use aria properties instead of accessibilityX --- .../bottom-tabs/src/views/BottomTabBar.tsx | 6 +- .../bottom-tabs/src/views/BottomTabItem.tsx | 70 +++++++++---------- packages/drawer/src/views/DrawerItem.tsx | 42 +++++------ .../drawer/src/views/DrawerToggleButton.tsx | 8 +-- .../elements/src/Header/HeaderBackButton.tsx | 8 ++- packages/elements/src/Header/HeaderButton.tsx | 4 +- packages/elements/src/Header/HeaderTitle.tsx | 2 +- packages/elements/src/PlatformPressable.tsx | 4 +- packages/elements/src/Screen.tsx | 3 +- packages/elements/src/types.tsx | 20 +++--- .../src/views/NativeStackView.native.tsx | 8 +-- packages/native/src/__tests__/Link.test.tsx | 8 +-- packages/native/src/useLinkProps.tsx | 2 +- .../src/utils/DrawerProgressContext.tsx | 4 +- .../src/utils/useDrawerProgress.tsx | 4 +- .../src/views/Drawer.tsx | 11 +-- .../src/views/Overlay.tsx | 18 ++--- .../react-native-tab-view/src/SceneView.tsx | 3 +- packages/react-native-tab-view/src/TabBar.tsx | 6 +- .../react-native-tab-view/src/TabBarItem.tsx | 39 +++++------ packages/react-native-tab-view/src/types.tsx | 12 ++-- .../src/views/Header/HeaderContainer.tsx | 5 +- .../stack/src/views/Header/HeaderSegment.tsx | 22 +++--- .../stack/src/views/Stack/CardContainer.tsx | 3 +- 24 files changed, 145 insertions(+), 167 deletions(-) diff --git a/packages/bottom-tabs/src/views/BottomTabBar.tsx b/packages/bottom-tabs/src/views/BottomTabBar.tsx index 829fa28f85..02662dd4da 100644 --- a/packages/bottom-tabs/src/views/BottomTabBar.tsx +++ b/packages/bottom-tabs/src/views/BottomTabBar.tsx @@ -331,7 +331,7 @@ export function BottomTabBar({ {tabBarBackgroundElement} {routes.map((route, index) => { @@ -368,7 +368,7 @@ export function BottomTabBar({ route.name ); - const accessibilityLabel = + const ariaLabel = options.tabBarAccessibilityLabel !== undefined ? options.tabBarAccessibilityLabel : typeof label === 'string' && Platform.OS === 'ios' @@ -389,7 +389,7 @@ export function BottomTabBar({ horizontal={hasHorizontalLabels} onPress={onPress} onLongPress={onLongPress} - accessibilityLabel={accessibilityLabel} + aria-label={ariaLabel} testID={options.tabBarButtonTestID} allowFontScaling={options.tabBarAllowFontScaling} activeTintColor={tabBarActiveTintColor} diff --git a/packages/bottom-tabs/src/views/BottomTabItem.tsx b/packages/bottom-tabs/src/views/BottomTabItem.tsx index fa6903cca5..68149df517 100644 --- a/packages/bottom-tabs/src/views/BottomTabItem.tsx +++ b/packages/bottom-tabs/src/views/BottomTabItem.tsx @@ -22,23 +22,23 @@ type Props = { /** * The route object which should be specified by the tab. */ - route: Route; + 'route': Route; /** * The `href` to use for the anchor tag on web */ - href?: string; + 'href'?: string; /** * Whether the tab is focused. */ - focused: boolean; + 'focused': boolean; /** * The descriptor object for the route. */ - descriptor: BottomTabDescriptor; + 'descriptor': BottomTabDescriptor; /** * The label text of the tab. */ - label: + 'label': | string | ((props: { focused: boolean; @@ -49,7 +49,7 @@ type Props = { /** * Icon to display for the tab. */ - icon: (props: { + 'icon': (props: { focused: boolean; size: number; color: string; @@ -57,74 +57,74 @@ type Props = { /** * Text to show in a badge on the tab icon. */ - badge?: number | string; + 'badge'?: number | string; /** * Custom style for the badge. */ - badgeStyle?: StyleProp; + 'badgeStyle'?: StyleProp; /** * The button for the tab. Uses a `Pressable` by default. */ - button?: (props: BottomTabBarButtonProps) => React.ReactNode; + 'button'?: (props: BottomTabBarButtonProps) => React.ReactNode; /** * The accessibility label for the tab. */ - accessibilityLabel?: string; + 'aria-label'?: string; /** * An unique ID for testing for the tab. */ - testID?: string; + 'testID'?: string; /** * Function to execute on press in React Native. * On the web, this will use onClick. */ - onPress: ( + 'onPress': ( e: React.MouseEvent | GestureResponderEvent ) => void; /** * Function to execute on long press. */ - onLongPress: (e: GestureResponderEvent) => void; + 'onLongPress': (e: GestureResponderEvent) => void; /** * Whether the label should be aligned with the icon horizontally. */ - horizontal: boolean; + 'horizontal': boolean; /** * Color for the icon and label when the item is active. */ - activeTintColor?: string; + 'activeTintColor'?: string; /** * Color for the icon and label when the item is inactive. */ - inactiveTintColor?: string; + 'inactiveTintColor'?: string; /** * Background color for item when its active. */ - activeBackgroundColor?: string; + 'activeBackgroundColor'?: string; /** * Background color for item when its inactive. */ - inactiveBackgroundColor?: string; + 'inactiveBackgroundColor'?: string; /** * Whether to show the label text for the tab. */ - showLabel?: boolean; + 'showLabel'?: boolean; /** * Whether to allow scaling the font for the label for accessibility purposes. */ - allowFontScaling?: boolean; + 'allowFontScaling'?: boolean; /** * Style object for the label element. */ - labelStyle?: StyleProp; + 'labelStyle'?: StyleProp; /** * Style object for the icon element. */ - iconStyle?: StyleProp; + 'iconStyle'?: StyleProp; /** * Style object for the wrapper element. */ - style?: StyleProp; + 'style'?: StyleProp; }; export function BottomTabItem({ @@ -141,7 +141,7 @@ export function BottomTabItem({ children, style, onPress, - accessibilityRole, + role, ...rest }: BottomTabBarButtonProps) => { return ( @@ -150,7 +150,7 @@ export function BottomTabItem({ android_ripple={{ borderless: true }} pressOpacity={1} href={href} - accessibilityRole={accessibilityRole} + role={role} onPress={onPress} style={style} > @@ -158,13 +158,13 @@ export function BottomTabItem({ ); }, - accessibilityLabel, + 'aria-label': ariaLabel, testID, onPress, onLongPress, horizontal, - activeTintColor: customActiveTintColor, - inactiveTintColor: customInactiveTintColor, + 'activeTintColor': customActiveTintColor, + 'inactiveTintColor': customInactiveTintColor, activeBackgroundColor = 'transparent', inactiveBackgroundColor = 'transparent', showLabel = true, @@ -262,19 +262,17 @@ export function BottomTabItem({ onPress, onLongPress, testID, - accessibilityLabel, - // FIXME: accessibilityRole: 'tab' doesn't seem to work as expected on iOS - accessibilityRole: Platform.select({ ios: 'button', default: 'tab' }), - accessibilityState: { selected: focused }, - // @ts-expect-error: keep for compatibility with older React Native versions - accessibilityStates: focused ? ['selected'] : [], - style: [ + 'aria-label': ariaLabel, + // FIXME: role: 'tab' doesn't seem to work as expected on iOS + 'role': Platform.select({ ios: 'button', default: 'tab' }), + 'aria-selected': focused, + 'style': [ styles.tab, { backgroundColor }, horizontal ? styles.tabLandscape : styles.tabPortrait, style, ], - children: ( + 'children': ( {renderIcon(scene)} {renderLabel(scene)} diff --git a/packages/drawer/src/views/DrawerItem.tsx b/packages/drawer/src/views/DrawerItem.tsx index c3879a44f6..d9ebdf8f2f 100644 --- a/packages/drawer/src/views/DrawerItem.tsx +++ b/packages/drawer/src/views/DrawerItem.tsx @@ -14,21 +14,21 @@ type Props = { /** * The route object which should be specified by the drawer item. */ - route: Route; + 'route': Route; /** * The `href` to use for the anchor tag on web */ - href?: string; + 'href'?: string; /** * The label text of the item. */ - label: + 'label': | string | ((props: { focused: boolean; color: string }) => React.ReactNode); /** * Icon to display for the `DrawerItem`. */ - icon?: (props: { + 'icon'?: (props: { focused: boolean; size: number; color: string; @@ -36,62 +36,62 @@ type Props = { /** * Whether to highlight the drawer item as active. */ - focused?: boolean; + 'focused'?: boolean; /** * Function to execute on press. */ - onPress: () => void; + 'onPress': () => void; /** * Color for the icon and label when the item is active. */ - activeTintColor?: string; + 'activeTintColor'?: string; /** * Color for the icon and label when the item is inactive. */ - inactiveTintColor?: string; + 'inactiveTintColor'?: string; /** * Background color for item when its active. */ - activeBackgroundColor?: string; + 'activeBackgroundColor'?: string; /** * Background color for item when its inactive. */ - inactiveBackgroundColor?: string; + 'inactiveBackgroundColor'?: string; /** * Color of the touchable effect on press. * Only supported on Android. * * @platform android */ - pressColor?: string; + 'pressColor'?: string; /** * Opacity of the touchable effect on press. * Only supported on iOS. * * @platform ios */ - pressOpacity?: number; + 'pressOpacity'?: number; /** * Style object for the label element. */ - labelStyle?: StyleProp; + 'labelStyle'?: StyleProp; /** * Style object for the wrapper element. */ - style?: StyleProp; + 'style'?: StyleProp; /** * Whether label font should scale to respect Text Size accessibility settings. */ - allowFontScaling?: boolean; + 'allowFontScaling'?: boolean; /** * Accessibility label for drawer item. */ - accessibilityLabel?: string; + 'aria-label'?: string; /** * ID to locate this drawer item in tests. */ - testID?: string; + 'testID'?: string; }; /** @@ -116,7 +116,7 @@ export function DrawerItem(props: Props) { pressColor, pressOpacity, testID, - accessibilityLabel, + 'aria-label': ariaLabel, ...rest } = props; @@ -137,9 +137,9 @@ export function DrawerItem(props: Props) { void; + 'onPress'?: () => void; /** * The `href` to use for the anchor tag on web */ - href?: string; + 'href'?: string; /** * Whether the button is disabled. */ - disabled?: boolean; + 'disabled'?: boolean; /** * Accessibility label for the button for screen readers. */ - accessibilityLabel?: string; + 'aria-label'?: string; /** * ID to locate this button in tests. */ - testID?: string; + 'testID'?: string; /** * Tint color for the header button. */ - tintColor?: string; + 'tintColor'?: string; /** * Color for material ripple (Android >= 5.0 only). */ - pressColor?: string; + 'pressColor'?: string; /** * Opacity when the button is pressed, used when ripple is not supported. */ - pressOpacity?: number; + 'pressOpacity'?: number; /** * Style object for the button. */ - style?: StyleProp; + 'style'?: StyleProp; /** * Content to render for the button. Usually the icon. */ - children: React.ReactNode; + 'children': React.ReactNode; }; export type HeaderBackButtonProps = Omit & { diff --git a/packages/native-stack/src/views/NativeStackView.native.tsx b/packages/native-stack/src/views/NativeStackView.native.tsx index 1f4ad69672..1758a41c25 100644 --- a/packages/native-stack/src/views/NativeStackView.native.tsx +++ b/packages/native-stack/src/views/NativeStackView.native.tsx @@ -364,13 +364,7 @@ const SceneView = ({ {headerBackground()} ) : null} - + { expect(toJSON()).toMatchInlineSnapshot(` { expect(toJSON()).toMatchInlineSnapshot(` { expect(toJSON()).toMatchInlineSnapshot(` { expect(toJSON()).toMatchInlineSnapshot(` ({ options?.config ) : undefined), - accessibilityRole: 'link' as const, + role: 'link' as const, onPress, }; } diff --git a/packages/react-native-drawer-layout/src/utils/DrawerProgressContext.tsx b/packages/react-native-drawer-layout/src/utils/DrawerProgressContext.tsx index 68e73a15c6..d94bb16ba4 100644 --- a/packages/react-native-drawer-layout/src/utils/DrawerProgressContext.tsx +++ b/packages/react-native-drawer-layout/src/utils/DrawerProgressContext.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import type Animated from 'react-native-reanimated'; +import type { SharedValue } from 'react-native-reanimated'; export const DrawerProgressContext = React.createContext< - Readonly> | undefined + Readonly> | undefined >(undefined); diff --git a/packages/react-native-drawer-layout/src/utils/useDrawerProgress.tsx b/packages/react-native-drawer-layout/src/utils/useDrawerProgress.tsx index 61eddb51dc..82e4fb7c0b 100644 --- a/packages/react-native-drawer-layout/src/utils/useDrawerProgress.tsx +++ b/packages/react-native-drawer-layout/src/utils/useDrawerProgress.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; -import type Animated from 'react-native-reanimated'; +import type { SharedValue } from 'react-native-reanimated'; import { DrawerProgressContext } from './DrawerProgressContext'; -export function useDrawerProgress(): Readonly> { +export function useDrawerProgress(): Readonly> { const progress = React.useContext(DrawerProgressContext); if (progress === undefined) { diff --git a/packages/react-native-drawer-layout/src/views/Drawer.tsx b/packages/react-native-drawer-layout/src/views/Drawer.tsx index 2bfa79ff29..d46bfa3136 100644 --- a/packages/react-native-drawer-layout/src/views/Drawer.tsx +++ b/packages/react-native-drawer-layout/src/views/Drawer.tsx @@ -415,14 +415,7 @@ export function Drawer({ > {children} @@ -432,7 +425,7 @@ export function Drawer({ progress={progress} onPress={() => toggleDrawer(false)} style={overlayStyle} - accessibilityLabel={overlayAccessibilityLabel} + aria-label={overlayAccessibilityLabel} /> ) : null} diff --git a/packages/react-native-drawer-layout/src/views/Overlay.tsx b/packages/react-native-drawer-layout/src/views/Overlay.tsx index 3088e8a870..5d9b729f87 100644 --- a/packages/react-native-drawer-layout/src/views/Overlay.tsx +++ b/packages/react-native-drawer-layout/src/views/Overlay.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { Platform, Pressable, StyleSheet } from 'react-native'; import Animated, { + type SharedValue, useAnimatedProps, useAnimatedStyle, } from 'react-native-reanimated'; @@ -8,9 +9,9 @@ import Animated, { const PROGRESS_EPSILON = 0.05; type Props = React.ComponentProps & { - progress: Animated.SharedValue; - onPress: () => void; - accessibilityLabel?: string; + 'progress': SharedValue; + 'onPress': () => void; + 'aria-label'?: string; }; export const Overlay = React.forwardRef(function Overlay( @@ -18,7 +19,7 @@ export const Overlay = React.forwardRef(function Overlay( progress, onPress, style, - accessibilityLabel = 'Close drawer', + 'aria-label': ariaLabel = 'Close drawer', ...props }: Props, ref: React.Ref @@ -36,9 +37,8 @@ export const Overlay = React.forwardRef(function Overlay( const active = progress.value > PROGRESS_EPSILON; return { - pointerEvents: active ? 'auto' : 'none', - accessibilityElementsHidden: !active, - importantForAccessibility: active ? 'auto' : 'no-hide-descendants', + 'pointerEvents': active ? 'auto' : 'none', + 'aria-hidden': !active, } as const; }); @@ -52,8 +52,8 @@ export const Overlay = React.forwardRef(function Overlay( ); diff --git a/packages/react-native-tab-view/src/SceneView.tsx b/packages/react-native-tab-view/src/SceneView.tsx index d31a1b15d3..ad00339daf 100644 --- a/packages/react-native-tab-view/src/SceneView.tsx +++ b/packages/react-native-tab-view/src/SceneView.tsx @@ -75,8 +75,7 @@ export function SceneView({ return ( ) => typeof route.accessible !== 'undefined' ? route.accessible : true; const getAccessibilityLabelDefault = ({ route }: Scene) => - typeof route.accessibilityLabel === 'string' - ? route.accessibilityLabel + typeof route['aria-label'] === 'string' + ? route['aria-label'] : typeof route.title === 'string' ? route.title : undefined; @@ -678,7 +678,7 @@ export function TabBar({ data={routes as Animated.WithAnimatedValue[]} keyExtractor={keyExtractor} horizontal - accessibilityRole="tablist" + role="tablist" keyboardShouldPersistTaps="handled" scrollEnabled={scrollEnabled} bounces={bounces} diff --git a/packages/react-native-tab-view/src/TabBarItem.tsx b/packages/react-native-tab-view/src/TabBarItem.tsx index 6583dee7f9..0b1004359f 100644 --- a/packages/react-native-tab-view/src/TabBarItem.tsx +++ b/packages/react-native-tab-view/src/TabBarItem.tsx @@ -92,19 +92,19 @@ type TabBarItemInternalProps = Omit< | 'getTestID' | 'getAccessible' > & { - isFocused: boolean; - index: number; - routesLength: number; - accessibilityLabel?: string; - label?: string; - testID?: string; - accessible?: boolean; + 'isFocused': boolean; + 'index': number; + 'routesLength': number; + 'aria-label'?: string; + 'label'?: string; + 'testID'?: string; + 'accessible'?: boolean; }; const TabBarItemInternal = ({ - accessibilityLabel, + 'aria-label': ariaLabel, accessible, - label: labelText, + 'label': labelText, testID, onLongPress, onPress, @@ -112,18 +112,18 @@ const TabBarItemInternal = ({ position, route, style, - inactiveColor: inactiveColorCustom, - activeColor: activeColorCustom, + 'inactiveColor': inactiveColorCustom, + 'activeColor': activeColorCustom, labelStyle, onLayout, - index: tabIndex, + 'index': tabIndex, pressColor, pressOpacity, renderBadge, renderIcon, defaultTabWidth, routesLength, - renderLabel: renderLabelCustom, + 'renderLabel': renderLabelCustom, android_ripple = { borderless: true }, }: TabBarItemInternalProps) => { const labelColorFromStyle = StyleSheet.flatten(labelStyle || {}).color; @@ -221,8 +221,7 @@ const TabBarItemInternal = ({ const scene = { route }; - accessibilityLabel = - typeof accessibilityLabel !== 'undefined' ? accessibilityLabel : labelText; + ariaLabel = typeof ariaLabel !== 'undefined' ? ariaLabel : labelText; const badge = renderBadge ? renderBadge(scene) : null; @@ -231,9 +230,9 @@ const TabBarItemInternal = ({ android_ripple={android_ripple} testID={testID} accessible={accessible} - accessibilityLabel={accessibilityLabel} - accessibilityRole="tab" - accessibilityState={{ selected: isFocused }} + role="tab" + aria-label={ariaLabel} + aria-selected={isFocused} pressColor={pressColor} pressOpacity={pressOpacity} unstable_pressDelay={0} @@ -276,7 +275,7 @@ export function TabBarItem(props: Props) { const scene = { route }; - const accessibilityLabel = getAccessibilityLabel(scene); + const ariaLabel = getAccessibilityLabel(scene); const label = getLabelText(scene); const testID = getTestID(scene); const accessible = getAccessible(scene); @@ -291,7 +290,7 @@ export function TabBarItem(props: Props) { route={route} index={tabIndex} routesLength={navigationState.routes.length} - accessibilityLabel={accessibilityLabel} + aria-label={ariaLabel} label={label} testID={testID} accessible={accessible} diff --git a/packages/react-native-tab-view/src/types.tsx b/packages/react-native-tab-view/src/types.tsx index 774a8cfa2c..d1c03bea81 100644 --- a/packages/react-native-tab-view/src/types.tsx +++ b/packages/react-native-tab-view/src/types.tsx @@ -4,12 +4,12 @@ import type { PagerViewProps } from 'react-native-pager-view'; export type LocaleDirection = 'ltr' | 'rtl'; export type Route = { - key: string; - icon?: string; - title?: string; - accessible?: boolean; - accessibilityLabel?: string; - testID?: string; + 'key': string; + 'icon'?: string; + 'title'?: string; + 'accessible'?: boolean; + 'aria-label'?: string; + 'testID'?: string; }; export type Event = { diff --git a/packages/stack/src/views/Header/HeaderContainer.tsx b/packages/stack/src/views/Header/HeaderContainer.tsx index 90aa94db15..b626d5eaca 100644 --- a/packages/stack/src/views/Header/HeaderContainer.tsx +++ b/packages/stack/src/views/Header/HeaderContainer.tsx @@ -165,10 +165,7 @@ export function HeaderContainer({ : undefined } pointerEvents={isFocused ? 'box-none' : 'none'} - accessibilityElementsHidden={!isFocused} - importantForAccessibility={ - isFocused ? 'auto' : 'no-hide-descendants' - } + aria-hidden={!isFocused} style={ // Avoid positioning the focused header absolutely // Otherwise accessibility tools don't seem to be able to find it diff --git a/packages/stack/src/views/Header/HeaderSegment.tsx b/packages/stack/src/views/Header/HeaderSegment.tsx index 13873798a2..f66489a5e0 100644 --- a/packages/stack/src/views/Header/HeaderSegment.tsx +++ b/packages/stack/src/views/Header/HeaderSegment.tsx @@ -162,18 +162,18 @@ export function HeaderSegment(props: Props) { ? (props) => left({ ...props, - backImage: headerBackImage, - accessibilityLabel: headerBackAccessibilityLabel, - testID: headerBackTestID, - allowFontScaling: headerBackAllowFontScaling, - onPress: onGoBack, - label: headerBackTitle, - truncatedLabel: headerTruncatedBackTitle, - labelStyle: [leftLabelStyle, headerBackTitleStyle], - onLabelLayout: handleLeftLabelLayout, - screenLayout: layout, + 'backImage': headerBackImage, + 'aria-label': headerBackAccessibilityLabel, + 'testID': headerBackTestID, + 'allowFontScaling': headerBackAllowFontScaling, + 'onPress': onGoBack, + 'label': headerBackTitle, + 'truncatedLabel': headerTruncatedBackTitle, + 'labelStyle': [leftLabelStyle, headerBackTitleStyle], + 'onLabelLayout': handleLeftLabelLayout, + 'screenLayout': layout, titleLayout, - canGoBack: Boolean(onGoBack), + 'canGoBack': Boolean(onGoBack), }) : undefined; diff --git a/packages/stack/src/views/Stack/CardContainer.tsx b/packages/stack/src/views/Stack/CardContainer.tsx index 4c1c707169..37d1543e99 100644 --- a/packages/stack/src/views/Stack/CardContainer.tsx +++ b/packages/stack/src/views/Stack/CardContainer.tsx @@ -248,8 +248,7 @@ function CardContainerInner({ gestureVelocityImpact={gestureVelocityImpact} transitionSpec={transitionSpec} styleInterpolator={cardStyleInterpolator} - accessibilityElementsHidden={!focused} - importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'} + aria-hidden={!focused} pointerEvents={active ? 'box-none' : pointerEvents} pageOverflowEnabled={headerMode !== 'float' && presentation !== 'modal'} preloaded={preloaded}