Search code examples
react-nativeuser-interfacemobileborderreact-tsx

How do I use the reverse border radius at the top of a card? (react native)


I'm trying to create a red box (as shown in the picture) that has a notch on the top edge in React Native. This notch should have rounded corners with an inverted border radius. Then I wanted to write a word in the notch, with the size of the notch depending on the length of the word. There will also be text on the box later. Everything that is black in the picture should be transparent so that the background can be adjusted later.

template

I've already tried countless times. But either the background of the notch was not transparent, or the corners were not as rounded as in the picture. Another problem I encountered was that not everything was in a rectangular container, but some elements were outside.

I've already tried it in html and css and it worked there too. But I can't get it to work in react native.


Solution

  • You can break the notch component into 3 pieces: the text piece, the left curve, and the right curve. Styling the text piece's borderBottomLeftRadius and the borderBottomRightRadius gets you more than half way there; but the outer edges are tricky.

    import { View, StyleSheet, Text } from 'react-native';
    import useLayout from '../hooks/useLayout';
    export default function Container({
      contentContainerStyle,
      backgroundColor,
      headerBackgroundColor,
      borderRadius,
      headerTextStyle,
      title,
      children,
    }) {
      const [headerLayout, onHeaderLayout] = useLayout();
    
      return (
        <View style={contentContainerStyle}>
          <View>{children}</View>
          <View style={styles.header}>
            {/* left curve */}
            <View
              style={{
                width: 20,
                height: '100%',
                backgroundColor: headerBackgroundColor,
              }}>
              <View
                style={{
                  borderTopRightRadius: borderRadius,
                  borderBottomRadius: borderRadius,
                  borderTopWidth: headerLayout.height,
                  borderTopColor: backgroundColor,
                }}
              />
            </View>
            {/* header */}
            <View
              style={{
                backgroundColor: headerBackgroundColor,
                padding: 5,
                paddingHorizontal: 20,
                borderBottomLeftRadius: borderRadius,
                borderBottomRightRadius: borderRadius,
              }}
              onLayout={onHeaderLayout}>
              <Text style={headerTextStyle}>{title}</Text>
            </View>
            {/* right curve */}
            <View
              style={{
                width: 20,
                height: '100%',
                backgroundColor: headerBackgroundColor,
              }}>
              <View
                style={{
                  borderTopLeftRadius: borderRadius,
                  borderBottomRadius: borderRadius,
                  borderTopWidth: headerLayout.height,
                  borderTopColor: backgroundColor,
                }}
              />
            </View>
          </View>
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      header: {
        position: 'absolute',
        flexDirection: 'row',
        width: '100%',
        alignItems: 'center',
        justifyContent: 'center',
        top: 0,
        zIndex: 3,
      },
    });
    

    Usage:

    import {
      Text,
      SafeAreaView,
      StyleSheet,
      View,
      ImageBackground,
    } from 'react-native';
    import Container from './components/Container';
    
    const borderRadius = 15;
    const backgroundColor = 'red';
    const headerBackgroundColor = 'black';
    export default function App() {
      return (
        <SafeAreaView style={styles.container}>
          <Container
            borderRadius={borderRadius}
            title="Hello World"
            headerBackgroundColor={headerBackgroundColor}
            backgroundColor={backgroundColor}
            contentContainerStyle={styles.innerContent}
            headerTextStyle={styles.headerText}>
            <>
              <Text style={styles.title}>Title</Text>
              <Text>{text}</Text>
            </>
          </Container>
        </SafeAreaView>
      );
    }
    
    const text =
      'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        backgroundColor: headerBackgroundColor,
        padding: 8,
      },
      title: {
        marginVertical: 24,
        fontSize: 18,
        fontWeight: 'bold',
        // textAlign: 'center',
      },
      innerContent: {
        backgroundColor,
        borderRadius,
        padding: 5,
        margin: 5,
        paddingVertical: 10,
      },
      headerText: {
        fontSize: 16,
        fontWeight: 'bold',
        color: 'white',
      },
    });
    

    demo