import React, { useState, useCallback, ReactNode, useMemo } from "react"; import { View, TouchableOpacity, Text, ViewStyle, TextStyle, TouchableOpacityProps } from "react-native"; import Animated, { useSharedValue, useAnimatedStyle, withSpring, withTiming, } from "react-native-reanimated"; import { themeColors, ThemeName } from "../constants/Colors"; import useTheme from "../hooks/useTheme"; const Collapsible = ({ title, titleStyle, triggerComponent, children, containerStyle, animationType = "spring", variant = "default", themeScheme, touchableOpacityStyle, style, defaultValue = false, width }: { title?: string; titleStyle?: TextStyle; defaultValue?: boolean triggerComponent?: ReactNode; children: ReactNode; containerStyle?: ViewStyle; touchableOpacityStyle?: TouchableOpacityProps["style"] backgroundColor?: string; animationType?: "spring" | "normal"; themeScheme?: "light" | "dark"; variant?: "default" | "secondary" | ThemeName; width?: ViewStyle["width"] style?: { backgroundColor?: string, color?: string, borderColor?: string, isFocused?: string, } }) => { const [open, setOpen] = useState(defaultValue); const height = useSharedValue(0); const measuredHeight = useSharedValue(0); const { currentTheme, themeScheme: defaultThemeScheme } = useTheme(); const colorStyle = useMemo(() => { if (style) { return { backgroundColor: style.backgroundColor ?? currentTheme.muted, color: style.backgroundColor ?? currentTheme.foreground, borderColor: style.backgroundColor ?? currentTheme.border, isFocused: style.backgroundColor ?? currentTheme.primary, } } if (variant === 'default') { return { backgroundColor: currentTheme.card, color: currentTheme.foreground, borderColor: currentTheme.input, isFocused: currentTheme.primary, }; } if (variant === 'secondary') { return { backgroundColor: currentTheme.secondary, color: currentTheme.secondary_foreground, borderColor: currentTheme.foreground, isFocused: currentTheme.primary, }; } const theme = themeColors.find((t) => t.name === variant)?.[themeScheme ?? defaultThemeScheme]; return { backgroundColor: theme?.card ?? currentTheme.input, color: theme?.foreground ?? currentTheme.foreground, borderColor: theme?.primary ?? currentTheme.border, isFocused: theme?.primary ?? currentTheme.primary, }; }, [currentTheme, themeScheme, defaultThemeScheme, variant]); const toggle = useCallback(() => { setOpen((prev) => !prev); if (animationType === "spring") { height.value = open ? withTiming(0) : withSpring(measuredHeight.value); } else { height.value = open ? withTiming(0) : withTiming(measuredHeight.value); } }, [open]); const animatedStyle = useAnimatedStyle(() => ({ height: measuredHeight.value === 0 ? undefined : height.value, overflow: "hidden", })); return ( {triggerComponent ? triggerComponent : {title} } { if (measuredHeight.value === 0) { const layoutHeight = e.nativeEvent.layout.height; measuredHeight.value = layoutHeight; } }, [])} style={[ { backgroundColor: colorStyle.backgroundColor, width }, containerStyle, animatedStyle]}> {children} ); }; export default Collapsible; const Component = ({ children, color }: { children: string | React.ReactNode, color: string, }) => { const renderText = (text: string, key?: number) => ( {text} ); if (typeof children === "string") { return ( <> {renderText(children as string)} ); } if (Array.isArray(children)) { return ( <> {children.map((child, index) => ( typeof child === "string" ? renderText(child, index) : child ))} ); } return <>{children}; };