Files
2026-07-13 12:30:54 +08:00

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,
},
});