TransitionPanel
A motion component that switches content with custom enter/exit animations.
9:41
signal_cellular_altwifibattery_full

Installation
$
Usage
Example.tsx
import { TransitionPanel } from './components/TransitionPanel';export default function App() {const [index, setIndex] = React.useState(0);return (<TransitionPanelactiveIndex={index}variants={{enter: { opacity: 0, y: 50 },center: { opacity: 1, y: 0 },exit: { opacity: 0, y: -50 }}}><View style={{ backgroundColor: 'red', height: 100 }} /><View style={{ backgroundColor: 'blue', height: 100 }} /></TransitionPanel>);}
Full Component Code
TransitionPanel.tsx
1import React from 'react';2import { StyleSheet, View, ViewStyle } from 'react-native';3import Animated, {4 Keyframe,5 LinearTransition,6 Easing,7 AnimatedProps8} from 'react-native-reanimated';910export type TransitionSpec = {11 opacity?: number;12 x?: number;13 y?: number;14 scale?: number;15 rotate?: string;16};1718export type TransitionPanelProps = AnimatedProps<ViewStyle> & {19 children: React.ReactNode[];20 activeIndex: number;21 transition?: {22 duration?: number;23 easing?: (value: number) => number;24 };25 variants?: {26 enter: TransitionSpec;27 center: TransitionSpec;28 exit: TransitionSpec;29 };30 style?: ViewStyle;31};3233const DEFAULT_VARIANTS = {34 enter: { opacity: 0, scale: 0.9 },35 center: { opacity: 1, scale: 1 },36 exit: { opacity: 0, scale: 0.9 },37};3839export function TransitionPanel({40 children,41 activeIndex,42 transition = { duration: 300 },43 variants = DEFAULT_VARIANTS,44 style,45 ...rest46}: TransitionPanelProps) {47 if (activeIndex < 0 || activeIndex >= React.Children.count(children)) {48 return null;49 }5051 // Convert separate x/y props to transform array for Reanimated52 const buildTransform = (spec: TransitionSpec) => {53 const transform: any[] = [];54 if (spec.x !== undefined) transform.push({ translateX: spec.x });55 if (spec.y !== undefined) transform.push({ translateY: spec.y });56 if (spec.scale !== undefined) transform.push({ scale: spec.scale });57 if (spec.rotate !== undefined) transform.push({ rotate: spec.rotate });58 return transform;59 };6061 const EnteringAnimation = new Keyframe({62 0: {63 opacity: variants.enter.opacity ?? 0,64 transform: buildTransform(variants.enter),65 },66 100: {67 opacity: variants.center.opacity ?? 1,68 transform: buildTransform(variants.center),69 easing: Easing.bezier(0.25, 0.1, 0.25, 1),70 },71 }).duration(transition.duration ?? 250);7273 const ExitingAnimation = new Keyframe({74 0: {75 opacity: variants.center.opacity ?? 1,76 transform: buildTransform(variants.center),77 },78 100: {79 opacity: variants.exit.opacity ?? 0,80 transform: buildTransform(variants.exit),81 easing: Easing.bezier(0.25, 0.1, 0.25, 1),82 },83 }).duration(transition.duration ?? 250);8485 return (86 <View style={[styles.container, style]} {...rest}>87 <Animated.View88 key={activeIndex}89 entering={EnteringAnimation}90 exiting={ExitingAnimation}91 layout={LinearTransition.springify().damping(20).stiffness(200)}92 style={styles.animatedChild}93 >94 {children[activeIndex]}95 </Animated.View>96 </View>97 );98}99100const styles = StyleSheet.create({101 container: {102 width: '100%',103 overflow: 'hidden',104 },105 animatedChild: {106 width: '100%',107 },108});
Props
| Name | Type | Default | Description |
|---|---|---|---|
| children | React.ReactNode[] | - | Array of content nodes to switch between |
| activeIndex | number | 0 | Index of the currently active child |
| variants | object | { enter, center, exit } | Animation variants for enter/center/exit states |
| transition | object | { duration: 300 } | Transition configuration |
