@brijen/react-native-multistep
Version:
A lightweight multi-step view component for React Native with smooth transitions using Reanimated.
245 lines (243 loc) • 7.32 kB
JavaScript
"use strict";
import { View, StyleSheet, Text, useWindowDimensions } from 'react-native';
import React, { useState, useRef, forwardRef, useImperativeHandle } from 'react';
import Button from "./Button.js";
import ProgressCircle from "./ProgressCircle.js";
import Animated, { FadeInLeft, Easing, FadeOutRight, LinearTransition } from 'react-native-reanimated';
import Step from "./Step.js";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
* A multi-step container for managing step-based navigation.
* It provides built-in navigation between steps with customizable buttons.
*
* @example
* ```tsx
* <MultiStep>
* <Step title="Step 1">
* <Text>Content for Step 1</Text>
* </Step>
* <Step title="Step 2">
* <Text>Content for Step 2</Text>
* </Step>
* </MultiStep>
* ```
*/
const MultiStep = /*#__PURE__*/forwardRef((props, ref) => {
const {
children,
prevButtonText,
nextButtonText,
prevButtonStyle,
nextButtonStyle,
prevButtonTextStyle,
nextButtonTextStyle,
prevButtonComponent,
nextButtonComponent,
tintColor,
globalStepTitleStyle,
globalNextStepTitleStyle,
progressCircleSize,
progressCircleThickness,
progressCircleColor,
progressCircleTrackColor,
progressCircleLabelStyle,
headerStyle,
globalStepContainerStyle,
fullScreenHeight,
buttonContainerStyle,
onFinalStepSubmit,
submitButtonText,
submitButtonTextStyle,
submitButtonStyle,
submitButtonComponent
} = props;
const COLOR = tintColor || '#DE3163';
const stepCount = React.useMemo(() => React.Children.count(children), [children]);
const [currentStep, setCurrentStep] = useState(0);
const flatListRef = useRef(null);
const {
width
} = useWindowDimensions();
const nextStep = () => {
if (currentStep < stepCount - 1) {
setCurrentStep(prev => {
const nextIndex = prev + 1;
flatListRef.current?.scrollToIndex({
index: nextIndex,
animated: true
});
return nextIndex;
});
}
};
const prevStep = () => {
if (currentStep > 0) {
setCurrentStep(prev => {
const prevIndex = prev - 1;
flatListRef.current?.scrollToIndex({
index: prevIndex,
animated: true
});
return prevIndex;
});
}
};
useImperativeHandle(ref, () => ({
nextStep,
prevStep,
scrollToStep: index => {
if (index >= 0 && index < stepCount) {
setCurrentStep(index);
flatListRef.current?.scrollToIndex({
index,
animated: true
});
}
}
}));
const {
isValid,
titles
} = React.useMemo(() => {
const extractedTitles = [];
let allValid = true;
React.Children.forEach(children, child => {
if (! /*#__PURE__*/React.isValidElement(child) || child.type !== Step) {
allValid = false;
return;
}
extractedTitles.push({
title: child.props.title || '',
stepTitleStyle: child.props.stepTitleStyle || {},
nextStepTitleStyle: child.props.nextStepTitleStyle || {},
titleComponent: child.props.titleComponent
});
});
return {
isValid: allValid,
titles: extractedTitles
};
}, [children]);
if (!isValid) {
if (__DEV__) console.error('MultiStep only accepts `Step` components as direct children.');
return null;
}
if (titles.length === 0) {
if (__DEV__) console.error('MultiStep requires at least one Step component.');
return null;
}
const currentTitle = titles[currentStep];
const isFinalStep = currentStep === stepCount - 1;
return /*#__PURE__*/_jsxs(View, {
style: [styles.multiStepContainer, fullScreenHeight && {
flex: 1
}],
children: [/*#__PURE__*/_jsxs(View, {
style: [styles.navigationHeader, headerStyle],
children: [/*#__PURE__*/_jsxs(Animated.View, {
style: styles.navigationItemWrapper,
entering: FadeInLeft.duration(300).easing(Easing.inOut(Easing.quad)),
exiting: FadeOutRight.duration(300).easing(Easing.inOut(Easing.quad)),
children: [currentTitle?.titleComponent ? currentTitle.titleComponent : /*#__PURE__*/_jsx(Text, {
style: [styles.currentStepTect, {
color: COLOR
}, globalStepTitleStyle, currentTitle?.stepTitleStyle],
children: currentTitle?.title
}), /*#__PURE__*/_jsx(Text, {
style: [styles.nextStepText, globalNextStepTitleStyle, currentTitle?.nextStepTitleStyle],
children: currentStep < stepCount - 1 ? `Next: ${titles[currentStep + 1]?.title}` : 'Completion'
})]
}, currentStep), /*#__PURE__*/_jsx(ProgressCircle, {
currentStep: currentStep + 1,
totalSteps: titles.length,
size: progressCircleSize,
progressCircleThickness: progressCircleThickness,
progressColor: progressCircleColor || COLOR,
trackColor: progressCircleTrackColor,
progressCircleLabelStyle: progressCircleLabelStyle
})]
}), /*#__PURE__*/_jsx(Animated.FlatList, {
ref: flatListRef,
data: React.Children.toArray(children),
horizontal: true,
pagingEnabled: true,
scrollEnabled: false,
showsHorizontalScrollIndicator: false,
keyExtractor: (_, index) => index.toString(),
renderItem: ({
item
}) => /*#__PURE__*/_jsx(View, {
style: [styles.stepContainer, {
width
}, globalStepContainerStyle],
children: item
}),
extraData: {
currentStep,
stepCount
},
itemLayoutAnimation: LinearTransition
}), /*#__PURE__*/_jsxs(View, {
style: [styles.buttonGroup, buttonContainerStyle],
children: [prevButtonComponent ?? /*#__PURE__*/_jsx(Button, {
title: prevButtonText || 'Back',
variant: "secondary",
tintColor: COLOR,
style: prevButtonStyle,
textStyle: prevButtonTextStyle,
onPress: prevStep,
disabled: currentStep === 0
}), !isFinalStep && (nextButtonComponent ?? /*#__PURE__*/_jsx(Button, {
title: nextButtonText || 'Next',
variant: "primary",
tintColor: COLOR,
style: nextButtonStyle,
textStyle: nextButtonTextStyle,
onPress: nextStep
})), isFinalStep && (submitButtonComponent ?? /*#__PURE__*/_jsx(Button, {
title: submitButtonText || 'Submit',
variant: "primary",
tintColor: COLOR,
style: submitButtonStyle,
textStyle: submitButtonTextStyle,
onPress: onFinalStepSubmit
}))]
})]
});
});
export default MultiStep;
const styles = StyleSheet.create({
multiStepContainer: {
gap: 15
},
navigationHeader: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
gap: 15,
width: '100%',
paddingHorizontal: 15
},
navigationItemWrapper: {
flex: 1,
gap: 10
},
stepContainer: {
paddingHorizontal: 15
},
currentStepTect: {
fontSize: 18,
fontWeight: '600'
},
nextStepText: {
color: '#45474B'
},
buttonGroup: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 15,
marginTop: 10
}
});
//# sourceMappingURL=MultiStep.js.map