80 lines
2.1 KiB
TypeScript
80 lines
2.1 KiB
TypeScript
import { PropsWithChildren, useState } from 'react';
|
|
import { StyleSheet, TouchableOpacity, Animated, Easing } from 'react-native';
|
|
|
|
import { ThemedText } from '@/components/ThemedText';
|
|
import { ThemedView } from '@/components/ThemedView';
|
|
import { IconSymbol } from '@/components/ui/IconSymbol';
|
|
import { Colors } from '@/constants/Colors';
|
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
|
|
|
export function Collapsible({
|
|
children,
|
|
title,
|
|
}: PropsWithChildren & { title: string }) {
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [animation] = useState(new Animated.Value(0));
|
|
const theme = useColorScheme() ?? 'light';
|
|
|
|
const toggleOpen = () => {
|
|
setIsOpen(!isOpen);
|
|
Animated.timing(animation, {
|
|
toValue: isOpen ? 0 : 1,
|
|
duration: 300,
|
|
easing: Easing.bezier(0.4, 0, 0.2, 1),
|
|
useNativeDriver: true,
|
|
}).start();
|
|
};
|
|
|
|
const rotateInterpolate = animation.interpolate({
|
|
inputRange: [0, 1],
|
|
outputRange: ['0deg', '90deg'],
|
|
});
|
|
|
|
return (
|
|
<ThemedView style={styles.container}>
|
|
<TouchableOpacity
|
|
style={styles.heading}
|
|
onPress={toggleOpen}
|
|
activeOpacity={0.7}
|
|
>
|
|
<Animated.View style={{ transform: [{ rotate: rotateInterpolate }] }}>
|
|
<IconSymbol
|
|
name="chevron.right"
|
|
size={18}
|
|
weight="medium"
|
|
color={theme === 'light' ? Colors.light.icon : Colors.dark.icon}
|
|
/>
|
|
</Animated.View>
|
|
|
|
<ThemedText type="defaultSemiBold">{title}</ThemedText>
|
|
</TouchableOpacity>
|
|
{isOpen && <ThemedView style={styles.content}>{children}</ThemedView>}
|
|
</ThemedView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
marginBottom: 16,
|
|
borderRadius: 12,
|
|
overflow: 'hidden',
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: 1 },
|
|
shadowOpacity: 0.05,
|
|
shadowRadius: 2,
|
|
elevation: 1,
|
|
padding: 12,
|
|
},
|
|
heading: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
gap: 8,
|
|
paddingVertical: 8,
|
|
},
|
|
content: {
|
|
marginTop: 8,
|
|
marginLeft: 26,
|
|
paddingBottom: 8,
|
|
},
|
|
});
|