I'm working on a React Native project where I need to display a progress indicator that outlines a non-circular view.
I tried using react-native-svg with the Circle component to create a circular progress indicator, but it didn't work as I wanted.
I need the progress indicator to fit an elliptical or rounded-rectangular shape.
Here’s a simplified version of my current approach using basic React Native components: https://snack.expo.dev/@audn/progress-border
What I'm trying to make:
What I have so far:
import { TouchableOpacity, Text, View, StyleSheet } from 'react-native';
import moment from 'moment';
import Svg, { Circle } from 'react-native-svg';
const DateComponent = () => {
const date = moment(new Date());
const dayName = date.format('dd').charAt(0);
const dayNumber = date.format('D');
const isFutureDate = date.isAfter(moment(), 'day');
const progress = 0.75;
const radius = 35;
const strokeWidth = 2;
const circumference = 2 * Math.PI * radius;
return (
<TouchableOpacity style={styles.container}>
<View style={styles.wrapper}>
<Svg height="70" width="70" viewBox="0 0 70 70">
<Circle
cx="35"
cy="35"
r={radius}
stroke="gray"
strokeWidth={strokeWidth}
fill="none"
opacity={0.2}
/>
<Circle
cx="35"
cy="35"
r={radius}
stroke="green"
strokeWidth={strokeWidth}
fill="none"
strokeDasharray={`${circumference} ${circumference}`}
strokeDashoffset={(1 - progress) * circumference}
strokeLinecap="round"
transform="rotate(-90, 35, 35)"
/>
</Svg>
<View style={styles.card}>
<Text style={styles.dayText}>{dayName}</Text>
<Text style={[styles.dateText, { color: isFutureDate ? '#757575' : 'black' }]}>
{dayNumber}
</Text>
</View>
</View>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
},
wrapper: {
justifyContent: 'center',
alignItems: 'center',
},
card: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 35,
height: 70,
width: 70,
},
dayText: {
fontSize: 14,
color: '#757575',
},
dateText: {
fontSize: 18,
fontWeight: 'bold',
},
});
export default DateComponent;
I drew a simple SVG
file and just switched in it to your code... Does this solution seem ok to you?
import React from 'react';
import { TouchableOpacity, StyleSheet, View } from 'react-native';
import Svg, { Rect, Text as SvgText } from 'react-native-svg';
import moment from 'moment';
const DateComponent = () => {
const date = moment(new Date());
const dayName = date.format('dd').charAt(0);
const dayNumber = date.format('D');
const isFutureDate = date.isAfter(moment(), 'day');
const progress = 0.50;
const radius = 30;
const strokeWidth = 3;
const rectWidth = 60;
const rectHeight = 80;
const perimeter = 1.6 * (rectWidth + rectHeight);
const progressLength = progress * perimeter;
return (
<View style={styles.screen}>
<TouchableOpacity style={styles.container}>
<Svg width="70" height="100" viewBox="0 0 70 100">
<Rect
x="5"
y="10"
width={rectWidth}
height={rectHeight}
rx={radius}
ry={radius}
fill="black"
stroke="gray"
strokeWidth={strokeWidth}
opacity={0.2}
/>
<Rect
x="5"
y="10"
width={rectWidth}
height={rectHeight}
rx={radius}
ry={radius}
fill="none"
stroke="green"
strokeWidth={strokeWidth}
strokeDasharray={`${progressLength} ${perimeter - progressLength}`}
strokeLinecap="round"
/>
<SvgText
x="35"
y="40"
textAnchor="middle"
fill="#858585"
fontSize="20"
fontFamily="Arial"
>
{dayName}
</SvgText>
<SvgText
x="35"
y="75"
textAnchor="middle"
fill={isFutureDate ? '#757575' : 'white'}
fontSize="20"
fontFamily="Arial"
>
{dayNumber}
</SvgText>
</Svg>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
screen: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
},
container: {
justifyContent: 'center',
alignItems: 'center',
width: 70,
height: 100,
},
});
export default DateComponent;
UPDATE
pathLength version
import React from 'react';
import { TouchableOpacity, StyleSheet, View } from 'react-native';
import Svg, { Rect, Text as SvgText } from 'react-native-svg';
import moment from 'moment';
const DateComponent = () => {
const date = moment(new Date());
const dayName = date.format('dd').charAt(0);
const dayNumber = date.format('D');
const isFutureDate = date.isAfter(moment(), 'day');
const progress = 0.50;
const radius = 30;
const strokeWidth = 3;
const rectWidth = 60;
const rectHeight = 80;
return (
<View style={styles.screen}>
<TouchableOpacity style={styles.container}>
<Svg width="70" height="100" viewBox="0 0 70 100">
<Rect
x="5"
y="10"
width={rectWidth}
height={rectHeight}
rx={radius}
ry={radius}
fill="black"
stroke="gray"
strokeWidth={strokeWidth}
opacity={0.2}
/>
<Rect
x="5"
y="10"
width={rectWidth}
height={rectHeight}
rx={radius}
ry={radius}
fill="none"
stroke="green"
strokeWidth={strokeWidth}
strokeDasharray={`${progress * 100} ${100 - (progress * 100)}`}
strokeLinecap="round"
pathLength="100"
/>
<SvgText
x="35"
y="40"
textAnchor="middle"
fill="#858585"
fontSize="20"
fontFamily="Arial"
>
{dayName}
</SvgText>
<SvgText
x="35"
y="75"
textAnchor="middle"
fill={isFutureDate ? '#757575' : 'white'}
fontSize="20"
fontFamily="Arial"
>
{dayNumber}
</SvgText>
</Svg>
</TouchableOpacity>
</View>
);
};
const styles = StyleSheet.create({
screen: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#f5f5f5',
},
container: {
justifyContent: 'center',
alignItems: 'center',
width: 70,
height: 100,
},
});
export default DateComponent;