Skip to content

Instantly share code, notes, and snippets.

@xogumon
Last active April 7, 2023 01:44
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Embed
What would you like to do?
Collapse and CollapseGroup (React Native)
import React, {
PropsWithChildren,
useState,
useCallback,
useContext,
useEffect,
useRef,
} from 'react';
import {
Pressable,
ViewStyle,
View,
Text,
InteractionManager,
ColorValue,
Animated,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
type CollapseProps = PropsWithChildren<{
style?: ViewStyle;
title: string;
titleSize?: number;
titleColor?: ColorValue;
titleIcon?: string;
iconSize?: number;
id: string;
open?: boolean;
onToggle?: (isOpen: boolean) => void;
}>;
type CollapseGroupProps = {
children: React.ReactNode;
};
type CollapseContextType = {
activeId: string | null;
toggle: (collapseId: string) => void;
};
const CollapseContext = React.createContext<CollapseContextType | null>(null);
const CollapseGroup = ({children}: CollapseGroupProps) => {
const [activeId, setActiveId] = useState<string | null>(null);
const toggle = useCallback((collapseId: string) => {
InteractionManager.runAfterInteractions(() => {
setActiveId(prevId => (prevId === collapseId ? null : collapseId));
});
}, []);
const contextValue: CollapseContextType = {
activeId,
toggle,
};
return (
<CollapseContext.Provider value={contextValue}>
{children}
</CollapseContext.Provider>
);
};
const Collapse = ({
title,
titleColor,
titleSize = 24,
titleIcon,
iconSize = 30,
children,
style,
id,
open,
onToggle,
}: CollapseProps) => {
const [currentId, setCurrentId] = useState<null | string>(null);
const context = useContext(CollapseContext);
const hasContext = !!context;
const {activeId, toggle} = hasContext
? context
: {
activeId: currentId,
toggle: (collapseId: string) =>
setCurrentId(prevId => (prevId === collapseId ? null : collapseId)),
};
const isOpen = activeId === id;
const viewOpacity = useRef(new Animated.Value(0)).current;
const rotateValue = useRef(new Animated.Value(0)).current;
useEffect(() => {
if (open) {
toggle(id);
}
}, [open]);
useEffect(() => {
Animated.parallel([
Animated.timing(rotateValue, {
toValue: !isOpen ? 0 : 1,
duration: 300,
useNativeDriver: true,
}),
Animated.timing(viewOpacity, {
toValue: !isOpen ? 0 : 1,
duration: 300,
useNativeDriver: true,
}),
]).start();
onToggle?.(isOpen);
}, [isOpen]);
return (
<View style={[{marginVertical: 10}, style]}>
<Pressable
onPress={() => {
toggle(uuid);
}}>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingVertical: 10,
}}>
{titleIcon && (
<Icon
name={titleIcon}
size={iconSize}
style={{marginRight: 5}}
color={titleColor}
/>
)}
<Text
style={[
{
flex: 1,
fontSize: titleSize,
fontWeight: 'bold',
color: titleColor,
},
]}>
{title}
</Text>
<Animated.View
style={[
{
transform: [
{
rotate: rotateValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '180deg'],
}),
},
],
},
]}>
<Icon name="expand-more" size={iconSize} color={titleColor} />
</Animated.View>
</View>
</Pressable>
{isOpen && (
<Animated.View
style={[
{
opacity: viewOpacity,
paddingBottom: isOpen ? 10 : 0,
},
]}
key={id}>
{children}
</Animated.View>
)}
</View>
);
};
export {Collapse, CollapseGroup};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment