Accessibility issue for custom buttons made from SVGs - react-native

I'm trying to add accessibility to my app (disclosure, using 0.59.8 version of RN). I have SVG images (each in separate TSX files, called like "IconClose") that I am using to make custom buttons. When I call these SVGs, I wrap them in a TouchableOpacity with style and an onPress to make them act as buttons. I tried adding an accessibilityLabel="close button" and setting the accessibilityRole and the accessibilityTraits to the TouchableOpacity. They work as buttons and do the navigation perfectly, however when I check the accessibility, I can tap on the button to select it, but I get nothing as far as the voice reader in iOS saying the label or the hint. I've tried wrapping it in a View, I tried adding titles and aria-label to the SVG. Is there a way to make these custom buttons voice reader accessible?
the IconClose button
import React from 'react';
import Svg, { Path } from 'svgs';
export const IconClose = (props: any) => (
<Svg viewBox="0 0 24 24" width="24" height="24" fill="#000000" {...props} >
<Path
d="M13.4 12l6.3-6.3c.4-.4.4-1 0-1.4s-1-.4-1.4 0L12 10.6 5.7 4.3c-.4-.4-1-.4-1.4 0s-.4 1 0 1.4l6.3 6.3-6.3 6.3c-.4.4-.4 1 0 1.4.2.2.4.3.7.3s.5-.1.7-.3l6.3-6.3 6.3 6.3c.2.2.5.3.7.3s.5-.1.7-.3c.4-.4.4-1 0-1.4L13.4 12z"
fill="#231f20"
/>
</Svg>
);
where I call the button
export const Page: FC<IPageProps> = ({ imageSource, location, title, info, actions, content, footer, onCloseButtonPress }) => {
const renderHeader = () => {
return (
<View style={styles.header}>
{imageSource
? <Image style={styles.image} source={imageSource} />
: <IconImage width={120} height={120} fill={COLORS.TECH_GRAY} />
}
<TouchableOpacity
style={styles.closeIcon}
onPress={onCloseButtonPress}
activeOpacity={0.8}
accessibilityComponentType="button"
accessibilityLabel="close">
<IconClose/>
</TouchableOpacity>
</View>
);
};

Related

Using expo vector icon in react native skia?

How to use expo vector icon in react native skia.
I want to use this icon,
<Ionicons name="md-checkmark-circle" size={32} color="green" /> in skia Canvas or Box
Thanks.
You can't use vector icons directly but you can add the icon as an SVG image like this:
import {
Canvas,
ImageSVG,
useSVG
} from "#shopify/react-native-skia";
const ImageSVGDemo = () => {
// Alternatively, you can pass an SVG URL directly
// for instance: const svg = useSVG("https://upload.wikimedia.org/wikipedia/commons/f/fd/Ghostscript_Tiger.svg");
const svg = useSVG(require("../../assets/checkmark.svg"));
return (
<Canvas style={{ flex: 1 }}>
{ svg && (
<ImageSVG
svg={svg}
x={0}
y={0}
width={256}
height={256}
/>)
}
</Canvas>
);
};
and the checkmark.svg file will be:
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 0c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-1.25 16.518l-4.5-4.319 1.396-1.435 3.078 2.937 6.105-6.218 1.421 1.409-7.5 7.626z"/></svg>

How would you do that opening effect with React Native?

Imagine a feed of items and then you click on one item and it opens in this way. We need to create a news app that has this identical animation (check sec 00:13 of video linked). enter link description here
You can use the react-native-collapsible package to achieve something like this
https://github.com/oblador/react-native-collapsible
One way to do this is to do dynamic styling.
like this:
import React from "react";
import { View, TouchableOpacity} from "react-native";
const Component = () => {
const [isOpen, setisOpen] = React.useState(false);
return (
<TouchableOpacity onPress={() => setisOpen(!isOpen)}>
<View>
{/* The View content that is always open */}
</View>
<View style={isOpen ? { disply: "flex" } : { disply: "none" }}>
{/* The View content to be opened/hidden */}
</View>
</TouchableOpacity>
);
};
then you can just import this component in your screen like this:
<Component />
Depend on the navigation library you are using but I will assume that you are using react-navigation.
You have this package to perform an animation with shared element during navigation between two screens: https://github.com/IjzerenHein/react-navigation-shared-element.
Very easy to use and very performant.

How to pass a function to a react native custom component but not run until onPress

I am new to RN. WRiting my first app. I read the docs on passing functions but am unclear on how to solve my issue. I have built myself a custom button component.
import React from 'react'
import { View, TouchableOpacity, Text } from 'react-native'
import { Theme } from '../Styles/Theme';
import { AntDesign } from '#expo/vector-icons'
export const CButton = ({text, icon = null, iconSide = null, onPress}) => {
return (
<TouchableOpacity onPress={onPress} >
<View style={Theme.primaryButton}>
{iconSide == "left" && icon &&
<AntDesign style={Theme.primaryButtonIcon} name={icon} size={24} color="white" />
}
<Text style={Theme.primaryButtonText}>{text}</Text>
{iconSide == "right" && icon &&
<AntDesign style={Theme.primaryButtonIcon} name={icon} size={24} color="white" />
}
</View>
</TouchableOpacity>
)
}
I think this is pretty basic stuff.
I am using the button as a login button in this case:
<CButton
text="Login"
icon="right"
iconSide="right"
onPress={login(email, password)}
/>
I am passing login(email, password) to the onPress prop and then using it in my custom component like:
<TouchableOpacity onPress={() => onPress} >
The issue is that as the way I am doing this is calling the function on load (I know thats a web term). Its not waiting until I press the button.
How do I make sure the onPress only happens when the button is pressed?
TIA
In your way, you are directly calling the login function when component is rendering.
You just need to modify your use of component like this
<CButton
text="Login"
icon="right"
iconSide="right"
onPress={()=> login(email, password)}
/>

ReactNative Cannot update a component from inside the function body of a > different component

My HomeHeader component is like this:
import React from "react";
import { View, StyleSheet, Text, Image } from "react-native";
import Icon from "react-native-vector-icons/FontAwesome5";
export default function HomeHeader({ navigation }) {
return (
<View style={styles.home_header}>
<Icon
style={styles.menu}
name="bars"
size={30}
color="white"
onPress={navigation.toggleDrawer()}
/>
<Image
style={styles.header_logo}
source={require("../assets/logo.png")}
/>
<Text>Hello</Text>
</View>
);
}
And am using it in my Home screen like this:
return (
<View>
<HomeHeader navigation={navigation} />
</View>
)
But am receiving this error message:
Warning: Cannot update a component from inside the function body of a
different component.
What am trying to do is, I have separated out the header section of my HOME screen into a separate component called HomeHeader. And in this component, am attaching the event handler to toggle the opening/closing of the DrawerNavigation (left side drawer menu)
If I create a Button in my HOME screen and add the event handler to toggle the drawer, it works fine. But the issue happens only if I try this from inside my HomeHeader component.
Btw, am using ReactNavigation v5 and I even tried this method: https://reactnavigation.org/docs/connecting-navigation-prop/
No luck so far.
Change onPress={ navigation.toggleDrawer() } to onPress={ ()=> navigation.toggleDrawer() }
You can read more about this in here

How to hide the statusBar when react-native modal shown?

I want to hide the status bar, when modal window is shown.
My setup is as following, but it won't work as expected:
<StatusBar animated={true} hidden={true} translucent={true}>
Use statusBarTranslucent
If your status bar is translucent, you can set statusBarTranslucent to the modal.
Added since React Native 0.62
<Modal {...props} statusBarTranslucent>...</Modal>
Credit: https://github.com/react-native-modal/react-native-modal/issues/50#issuecomment-607535322
This is a known issue and there seems to be no official/React way to fix it yet. You can follow the discussion here:
https://github.com/facebook/react-native/issues/7474
I saw a post in this discussion which proposes a hack to hide it, but I haven't tried it on my project. You can also upvote this trick if it works for you.
<View style={styles.outerContainer}
<View style={styles.container}>
<StatusBar hidden={true}/>
<View style={styles.content}>
</View>
<Modal animation={fade} transparent={true}>
{/*Modal Contents Here*/}
</Modal>
</View>
A more solid fix may be changing the theme of activity in native android code.
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.ReactNative.AppCompat.Light.NoActionBar.FullScreen">
<!-- Customize your theme here. -->
</style>
<style name="AppTheme.Launcher">
<item name="android:windowBackground">#drawable/launch_screen</item>
</style>
</resources>
Credits go to Traviskn and mbashiq who proposed fixes above. I recommend you to subscribe that issue.
According to the documentations, you should be able to hide status bar in both iOS and Android using this
import {StatusBar} from 'react-native';
StatusBar.setHidden(true);
We can use the background of StatusBar to solve this problem easily but may not the best.
<Modal transparent>
{Platform.OS === 'android' ?
<StatusBar backgroundColor="rgba(0,0,0,0.5)"/>
: null
}
<View style={{backgroundColor: 'rgba(0,0,0,0.5)'}}>
// ModalContent here
</View>
</Modal>
Just use the same background and this problem can be solved.
I am actually facing the same issue for some time, I tried many solutions but I didn't get rid of this problem. I also tried to use native Android code to hide the StatusBar for a single component it works in other component but when I use it in modal it just not working. So, at last, I got a solution that works for me. I remove the modal view and replace it with react-navigation to navigate to a specific path and handle the back button using BackHandler component.
i achieve this creating a custom status bar component with a modal prop:
import React from 'react'
import { StatusBar } from 'react-native'
const MyStatusBar = (props) => {
const { backgroundColor } = props
const { barStyle } = props
const { translucent } = props
const { hidden } = props
const { showHideTransition } = props
const { modal } = props;
(modal !== undefined) ? StatusBar.setHidden(true) : StatusBar.setHidden(false)
return (
<StatusBar showHideTransition={showHideTransition} hidden={hidden} translucent={translucent} backgroundColor={backgroundColor} barStyle={barStyle} />
)
}
export default MyStatusBar
inside my base component modal prop is undefined so custom status bar is shown:
<MyStatusBar backgroundColor={theme.colors.primary} barStyle={'light-content'} />
and then calling inside the component who call the modal:
<MyStatusBar modal={modalVisible ? true : undefined} />
I think the root of my problem is the same, but it appeared a little different than how it is described above.
Expected behaviour: When the Modal becomes visible the StatusBar should hide.
const [showModal, setShowModal] = useState(false)
...
<Modal
visible={showModal}
>
<StatusBar hidden={showModal} />
...
Actual bahviour: Sometimes the StatusBardissapears as expected, other times just the StatusBar background color goes away and the actual StatusBar remains.
Workaround: Due to the flickering behaviour I think the problem is a racing condition of the native Android dialog. Therefore, I built a custom Modal component that uses the StatusBar imperative api to make sure the StatusBar hide call is made before the Modal appears. So far the Problem has not reappeared.
Here is the custom Modal component:
const Modal = ({ visible, children, ...rest }) => {
const [modalVisibility, setModalVisibility] = useState(false);
useEffect(() => {
if (visible) {
StatusBar.setHidden(true);
setModalVisibility(true);
} else {
StatusBar.setHidden(false);
setModalVisibility(false);
}
}, [visible]);
return (
<RNModal
visible={modalVisibility}
{...rest}
>
{children}
</RNModal>
);
};
export default Modal;
Hello you can try this
<View style={styles.outerContainer}
<View style={styles.container}>
<StatusBar hidden={true}/>
<View style={styles.content}>
</View>
<Modal animation={fade} transparent={true}>
{/* Contents Here*/}
</Modal>
</View>
<StatusBar backgroundColor={'transparent'} translucent={true} />