Skip to content

Instantly share code, notes, and snippets.

@dilipsuthar97
Created April 3, 2025 06:29
Show Gist options
  • Save dilipsuthar97/26bfae34ef2bb21aabf194a02c10e54e to your computer and use it in GitHub Desktop.
Save dilipsuthar97/26bfae34ef2bb21aabf194a02c10e54e to your computer and use it in GitHub Desktop.
An accordion component using react-native-reanimated
import React, { FC, memo, ReactNode, useEffect } from 'react';
import { StyleProp, StyleSheet, Text, TextStyle, TouchableOpacity, View, ViewStyle } from 'react-native';
import Animated, {
Extrapolation,
interpolate,
measure,
runOnUI,
useAnimatedRef,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
withTiming,
} from 'react-native-reanimated';
import { Colors, Icons, Matrics } from '../../CommonConfig';
interface AccordionProps {
children?: ReactNode;
title?: string;
head?: ReactNode;
description?: string;
titleStyle?: StyleProp<TextStyle>;
contentStyle?: any;
style?: StyleProp<ViewStyle>;
isDefaultOpen?: boolean;
hideChevron?: boolean;
}
const Accordion: FC<AccordionProps> = ({
title,
head,
description,
children,
titleStyle,
contentStyle,
style,
isDefaultOpen,
hideChevron,
}) => {
const _refContent = useAnimatedRef<Animated.View>();
const heightValue = useSharedValue(0);
const open = useSharedValue(false);
const progress = useDerivedValue(() => (open.value ? withTiming(1) : withTiming(0)));
const iconStyle = useAnimatedStyle(() => ({
transform: [{ rotate: `${progress.value * -180}deg` }],
}));
const contentContainerStyle = useAnimatedStyle(() => ({
height: interpolate(progress.value, [0, 1], [0, heightValue.value], Extrapolation.CLAMP),
}));
useEffect(() => {
if (isDefaultOpen) {
open.value = true;
}
}, [isDefaultOpen]);
const onPressTitle = () => {
if (heightValue.value === 0) {
runOnUI(() => {
'worklet';
heightValue.value = measure(_refContent)?.height ?? 0;
})();
}
open.value = !open.value;
};
return (
<View style={[styles.container, style]}>
<TouchableOpacity onPress={onPressTitle}>
<View style={styles.headContainer}>
{head ?? (
<View style={styles.titleContainer}>
<Text style={[styles.title, titleStyle]}>{title}</Text>
{!hideChevron ? (
<Animated.Image
source={Icons.IC_CHEVRON_DOWN}
style={[styles.icon, iconStyle]}
tintColor={Colors.PRIMARY}
/>
) : (
<></>
)}
</View>
)}
</View>
</TouchableOpacity>
{/* content view */}
<Animated.View style={contentContainerStyle}>
<Animated.View
ref={_refContent}
onLayout={e => {
heightValue.value = e.nativeEvent.layout.height;
}}
style={styles.contentContainer}>
<View style={[styles.content, contentStyle]}>{children ?? <Text>{description}</Text>}</View>
</Animated.View>
</Animated.View>
</View>
);
};
export default memo(Accordion);
const styles = StyleSheet.create({
container: {
overflow: 'hidden',
backgroundColor: Colors.surfaceColor,
width: Matrics.screenWidth / 2.5,
alignSelf: 'center',
borderRadius: Matrics.ms(12),
},
headContainer: {
height: Matrics.mvs(60),
justifyContent: 'center',
paddingHorizontal: Matrics.ms(16),
},
titleContainer: {
flexDirection: 'row',
alignItems: 'center',
},
title: {
fontSize: Matrics.ms(15),
flex: 1,
color: Colors.colorTextPrimary,
textAlign: 'left',
},
icon: {
width: Matrics.ms(20),
height: Matrics.ms(20),
},
contentContainer: {
position: 'absolute',
top: 0,
width: '100%',
},
content: {
padding: Matrics.ms(15),
paddingTop: 0,
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment