├── .gitignore ├── .npmignore ├── src ├── components │ ├── UI │ │ ├── index.ts │ │ └── ProgressButton.tsx │ ├── createProcess.tsx │ ├── index.ts │ ├── Step.tsx │ ├── ProcessFooter.tsx │ ├── StepIcon.tsx │ └── ProcessFlow.tsx ├── hooks │ ├── index.ts │ └── useProcess.ts ├── context │ ├── index.ts │ └── ProcessContextProvider.tsx └── index.ts ├── types ├── components │ ├── UI │ │ ├── index.d.ts │ │ └── ProgressButton.d.ts │ ├── index.d.ts │ ├── createProcess.d.ts │ ├── Step.d.ts │ ├── StepIcon.d.ts │ ├── ProcessFooter.d.ts │ └── ProcessFlow.d.ts ├── hooks │ ├── index.d.ts │ └── useProcess.d.ts ├── context │ ├── index.d.ts │ └── ProcessContextProvider.d.ts └── index.d.ts ├── babel.config.js ├── assets └── iOS.gif ├── rollup.config.js ├── LICENSE ├── package.json ├── README.md └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | 3 | node_modules 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | assets 3 | src 4 | types 5 | 6 | rollup.config.js -------------------------------------------------------------------------------- /src/components/UI/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./ProgressButton"; 2 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { default as useProcess } from "./useProcess"; 2 | -------------------------------------------------------------------------------- /types/components/UI/index.d.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./ProgressButton"; 2 | -------------------------------------------------------------------------------- /types/hooks/index.d.ts: -------------------------------------------------------------------------------- 1 | export { default as useProcess } from "./useProcess"; 2 | -------------------------------------------------------------------------------- /src/context/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ProcessContainer } from "./ProcessContextProvider"; 2 | -------------------------------------------------------------------------------- /types/context/index.d.ts: -------------------------------------------------------------------------------- 1 | export { default as ProcessContainer } from "./ProcessContextProvider"; 2 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["module:metro-react-native-babel-preset"], 3 | }; 4 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./components"; 2 | export * from "./context"; 3 | export * from "./hooks"; 4 | -------------------------------------------------------------------------------- /assets/iOS.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harshilmiyani/react-native-step-by-step-process/HEAD/assets/iOS.gif -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./components"; 2 | export * from "./context"; 3 | export * from "./hooks"; 4 | -------------------------------------------------------------------------------- /types/hooks/useProcess.d.ts: -------------------------------------------------------------------------------- 1 | import { ProcessContextType } from '../context/ProcessContextProvider'; 2 | declare const useProcess: () => ProcessContextType; 3 | export default useProcess; 4 | -------------------------------------------------------------------------------- /src/components/createProcess.tsx: -------------------------------------------------------------------------------- 1 | import ProcessFlow from "./ProcessFlow"; 2 | import Step from "./Step"; 3 | 4 | const createProcess = () => { 5 | return { 6 | ProcessFlow, 7 | Step, 8 | }; 9 | }; 10 | 11 | export default createProcess; 12 | -------------------------------------------------------------------------------- /src/hooks/useProcess.ts: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { ProcessContext, ProcessContextType } from '../context/ProcessContextProvider'; 3 | 4 | const useProcess = () => { 5 | const process = useContext(ProcessContext); 6 | return process; 7 | }; 8 | 9 | export default useProcess; 10 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ProcessFlow } from "./ProcessFlow"; 2 | export { default as ProcessFooter } from "./ProcessFooter"; 3 | export { default as Step } from "./Step"; 4 | export { default as StepIcon } from "./StepIcon"; 5 | export { default as createProcess } from "./createProcess"; 6 | export { default as ProgressButton } from "./UI/ProgressButton"; 7 | -------------------------------------------------------------------------------- /types/components/index.d.ts: -------------------------------------------------------------------------------- 1 | export { default as ProcessFlow } from "./ProcessFlow"; 2 | export { default as ProcessFooter } from "./ProcessFooter"; 3 | export { default as Step } from "./Step"; 4 | export { default as StepIcon } from "./StepIcon"; 5 | export { default as createProcess } from "./createProcess"; 6 | export { default as ProgressButton } from "./UI/ProgressButton"; 7 | -------------------------------------------------------------------------------- /types/components/UI/ProgressButton.d.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { StyleProp, ViewStyle } from "react-native"; 3 | interface ProgressButtonProps { 4 | title?: string; 5 | onPress?: () => {}; 6 | style?: ViewStyle; 7 | titleStyle?: StyleProp; 8 | disabled?: boolean; 9 | children?: ReactNode; 10 | } 11 | declare const ProgressButton: ({ title, onPress, style, titleStyle, disabled, children, }: ProgressButtonProps) => JSX.Element; 12 | export default ProgressButton; 13 | -------------------------------------------------------------------------------- /types/components/createProcess.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare const createProcess: () => { 3 | ProcessFlow: ({ children, onChangeStep, onNext, onPrevious, onSubmit, nextButtonStyle, nextButtonText, nextButtonTextStyle, previousButtonStyle, previousButtonText, previousButtonTextStyle, finishButtonText, nextButtonDisabled, previousButtonDisabled, removeButtonRow, footerComponent, labelStyle, ...props }: import("./ProcessFlow").ProcessFlowProps) => JSX.Element; 4 | Step: ({ children }: import("./Step").StepProps) => JSX.Element; 5 | }; 6 | export default createProcess; 7 | -------------------------------------------------------------------------------- /types/components/Step.d.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { TextStyle } from 'react-native'; 3 | export interface StepProps { 4 | children?: ReactNode; 5 | hide?: boolean; 6 | label?: string; 7 | labelStyle?: TextStyle | undefined; 8 | onPrevious?: (previousStepIndex: number) => {}; 9 | onNext?: (nextStepIndex: number) => {}; 10 | hideNextButton?: boolean; 11 | hidePreviousButton?: boolean; 12 | footerComponent?: ReactNode; 13 | showFirstStepPreviousButton?: boolean; 14 | } 15 | declare const Step: ({ children }: StepProps) => JSX.Element; 16 | export default Step; 17 | -------------------------------------------------------------------------------- /types/context/ProcessContextProvider.d.ts: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | interface ProcessContainerProps { 3 | children: ReactNode; 4 | initialNumberOfSteps: number; 5 | } 6 | export interface ProcessContextType { 7 | totalSteps: number; 8 | numberOfSteps: (totalStepsNumber: number) => void; 9 | activeStep: number; 10 | currentStep: (currentStepIndex: number) => void; 11 | } 12 | export declare const ProcessContext: React.Context; 13 | declare const ProcessContainer: ({ children, initialNumberOfSteps }: ProcessContainerProps) => JSX.Element; 14 | export default ProcessContainer; 15 | -------------------------------------------------------------------------------- /src/components/Step.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import { TextStyle } from 'react-native'; 3 | 4 | export interface StepProps { 5 | children?: ReactNode; 6 | hide?: boolean; 7 | label?: string; 8 | labelStyle?: TextStyle | undefined; 9 | onPrevious?: (previousStepIndex: number) => {}; 10 | onNext?: (nextStepIndex: number) => {}; 11 | hideNextButton?: boolean; 12 | hidePreviousButton?: boolean; 13 | footerComponent?: ReactNode; 14 | showFirstStepPreviousButton?: boolean; 15 | } 16 | const Step = ({ children }: StepProps) => { 17 | return <>{children}; 18 | }; 19 | 20 | export default Step; 21 | -------------------------------------------------------------------------------- /types/components/StepIcon.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { TextStyle } from "react-native"; 3 | export interface StepIconProps { 4 | label?: string; 5 | labelStyle?: TextStyle; 6 | stepNumber?: number; 7 | isActiveStep?: boolean; 8 | isCompletedStep?: boolean; 9 | completedStepIconColor?: string; 10 | activeStepIconColor?: string; 11 | activeStepNumColor?: string; 12 | disabledStepNumColor?: string; 13 | completedCheckColor?: string; 14 | } 15 | declare const StepIcon: ({ label, labelStyle, stepNumber, isActiveStep, isCompletedStep, completedStepIconColor, activeStepIconColor, activeStepNumColor, disabledStepNumColor, completedCheckColor, }: StepIconProps) => JSX.Element; 16 | export default StepIcon; 17 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "@rollup/plugin-node-resolve"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import dts from "rollup-plugin-dts"; 4 | import typescript from "@rollup/plugin-typescript"; 5 | 6 | const packageJson = require("./package.json"); 7 | 8 | export default [ 9 | { 10 | input: "src/index.ts", 11 | output: [ 12 | { 13 | file: packageJson.main, 14 | format: "cjs", 15 | sourcemap: true, 16 | }, 17 | { 18 | file: packageJson.module, 19 | format: "esm", 20 | sourcemap: true, 21 | }, 22 | ], 23 | external: ["react", "react-native"], 24 | plugins: [ 25 | resolve(), 26 | commonjs(), 27 | typescript({ 28 | tsconfig: "./tsconfig.json", 29 | declaration: false, 30 | emitDeclarationOnly: false, 31 | declarationDir: undefined, 32 | }), 33 | ], 34 | }, 35 | { 36 | input: "types/index.d.ts", 37 | output: [{ file: "dist/index.d.ts", format: "esm" }], 38 | plugins: [dts.default()], 39 | }, 40 | ]; 41 | -------------------------------------------------------------------------------- /types/components/ProcessFooter.d.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { TextStyle, ViewStyle } from "react-native"; 3 | export interface ProcessFooterProps { 4 | removeButtonRow?: boolean; 5 | hidePreviousButton?: boolean | undefined; 6 | previousButtonStyle?: ViewStyle; 7 | previousButtonText?: string; 8 | previousButtonDisabled?: boolean; 9 | onPreviousStep?: () => {}; 10 | previousButtonTextStyle?: TextStyle; 11 | hideNextButton?: boolean | undefined; 12 | nextButtonStyle?: ViewStyle; 13 | nextButtonTitle?: string; 14 | onNextStep?: () => {}; 15 | nextButtonTextStyle?: TextStyle; 16 | nextButtonDisabled?: boolean; 17 | footerComponent?: ReactNode; 18 | } 19 | declare const ProcessFooter: ({ removeButtonRow, hidePreviousButton, previousButtonStyle, previousButtonText, previousButtonDisabled, onPreviousStep, previousButtonTextStyle, hideNextButton, nextButtonStyle, nextButtonTitle, onNextStep, nextButtonTextStyle, nextButtonDisabled, footerComponent, }: ProcessFooterProps) => JSX.Element | null; 20 | export default ProcessFooter; 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Harshil Miyani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /types/components/ProcessFlow.d.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode, ReactElement } from "react"; 2 | import { ProcessFooterProps } from "./ProcessFooter"; 3 | import { StepProps } from "./Step"; 4 | import { StepIconProps } from "./StepIcon"; 5 | export interface ProcessFlowProps extends StepProps, ProcessFooterProps, StepIconProps { 6 | children: ReactElement[]; 7 | onChangeStep?: (currentStepIndex?: number, nextStepIndex?: number, previousStepIndex?: number) => {} | undefined; 8 | onPrevious: (previousStepIndex: number) => {}; 9 | onNext: (nextStepIndex: number) => {}; 10 | onSubmit?: () => {}; 11 | nextButtonText?: string; 12 | previousButtonText?: string; 13 | finishButtonText?: string; 14 | nextButtonDisabled?: boolean; 15 | previousButtonDisabled?: boolean; 16 | removeButtonRow?: boolean; 17 | footerComponent?: ReactNode; 18 | } 19 | declare const ProcessFlow: ({ children, onChangeStep, onNext, onPrevious, onSubmit, nextButtonStyle, nextButtonText, nextButtonTextStyle, previousButtonStyle, previousButtonText, previousButtonTextStyle, finishButtonText, nextButtonDisabled, previousButtonDisabled, removeButtonRow, footerComponent, labelStyle, ...props }: ProcessFlowProps) => JSX.Element; 20 | export default ProcessFlow; 21 | -------------------------------------------------------------------------------- /src/context/ProcessContextProvider.tsx: -------------------------------------------------------------------------------- 1 | import React, { createContext, ReactNode, useState } from 'react'; 2 | 3 | interface ProcessContainerProps { 4 | children: ReactNode; 5 | initialNumberOfSteps: number; 6 | } 7 | 8 | export interface ProcessContextType { 9 | totalSteps: number; 10 | numberOfSteps: (totalStepsNumber: number) => void; 11 | activeStep: number; 12 | currentStep: (currentStepIndex: number) => void; 13 | } 14 | 15 | export const ProcessContext = createContext({ 16 | totalSteps: 0, 17 | numberOfSteps: (_) => {}, 18 | activeStep: 0, 19 | currentStep: (_) => {} 20 | }); 21 | 22 | const ProcessContainer = ({ children, initialNumberOfSteps = 1 }: ProcessContainerProps) => { 23 | const [activeStep, setActiveStep] = useState(0); 24 | const [totalSteps, setTotalSteps] = useState(initialNumberOfSteps); 25 | 26 | const numberOfSteps = (totalStepsNumber: number) => { 27 | if (totalStepsNumber > 0) { 28 | setTotalSteps(totalStepsNumber); 29 | 30 | if (activeStep >= totalStepsNumber) { 31 | setActiveStep(totalStepsNumber - 1); 32 | } 33 | } else { 34 | console.error('Please Enter Positive Number'); 35 | } 36 | }; 37 | 38 | const currentStep = (currentStepIndex: number) => { 39 | if (currentStepIndex >= 0 && currentStepIndex < totalSteps) { 40 | setActiveStep(currentStepIndex); 41 | } else { 42 | setActiveStep(0); 43 | } 44 | }; 45 | 46 | const value: ProcessContextType = { 47 | totalSteps, 48 | numberOfSteps, 49 | activeStep, 50 | currentStep 51 | }; 52 | return {children}; 53 | }; 54 | 55 | export default ProcessContainer; 56 | -------------------------------------------------------------------------------- /src/components/UI/ProgressButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import { 3 | Pressable, 4 | StyleProp, 5 | StyleSheet, 6 | Text, 7 | ViewStyle, 8 | } from "react-native"; 9 | import { RFValue } from "react-native-responsive-fontsize"; 10 | 11 | interface ProgressButtonProps { 12 | title?: string; 13 | onPress?: () => {}; 14 | style?: ViewStyle; 15 | titleStyle?: StyleProp; 16 | disabled?: boolean; 17 | children?: ReactNode; 18 | } 19 | const ProgressButton = ({ 20 | title, 21 | onPress, 22 | style, 23 | titleStyle, 24 | disabled, 25 | children, 26 | }: ProgressButtonProps) => { 27 | return ( 28 | [ 32 | pressed && styles.pressed, 33 | styles.buttonStyle, 34 | { 35 | ...style, 36 | backgroundColor: disabled 37 | ? "#cdcdcd" 38 | : style?.backgroundColor ?? "black", 39 | minWidth: 100, 40 | borderRadius: 8, 41 | }, 42 | ]} 43 | > 44 | {children} 45 | {title && ( 46 | 52 | {title} 53 | 54 | )} 55 | 56 | ); 57 | }; 58 | 59 | export default ProgressButton; 60 | 61 | const styles = StyleSheet.create({ 62 | buttonStyle: { 63 | justifyContent: "center", 64 | alignItems: "center", 65 | height: "60%", 66 | }, 67 | title: { 68 | textAlign: "center", 69 | color: "white", 70 | fontSize: RFValue(16), 71 | fontWeight: "bold", 72 | paddingHorizontal: "4%", 73 | }, 74 | pressed: { opacity: 0.3 }, 75 | }); 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-step-by-step-process", 3 | "version": "1.4.0", 4 | "description": "React Native Step by Step Process Library", 5 | "main": "dist/cjs/index.js", 6 | "module": "dist/esm/index.js", 7 | "types": "dist/index.d.ts", 8 | "type": "module", 9 | "files": [ 10 | "dist" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/harshilmiyani/react-native-step-by-step-process.git" 15 | }, 16 | "homepage": "https://github.com/harshilmiyani/react-native-step-by-step-process#readme", 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1", 19 | "build": "rm -rf dist/ && npm run build:esm && npm run build:cjs", 20 | "build:esm": "tsc", 21 | "build:cjs": "tsc --module CommonJS --outDir dist/cjs", 22 | "rollup": "tsc && rollup -c rollup.config.js --bundleConfigAsCjs" 23 | }, 24 | "keywords": [ 25 | "react", 26 | "react-native", 27 | "native", 28 | "step", 29 | "by", 30 | "step", 31 | "process", 32 | "rn", 33 | "react component", 34 | "react native component", 35 | "react native", 36 | "mobile", 37 | "ios", 38 | "android", 39 | "ui", 40 | "stepper", 41 | "progress", 42 | "progress steps", 43 | "react-component", 44 | "react-native-component", 45 | "react-stepper", 46 | "react-native-stepper", 47 | "react-native-stepper-ui", 48 | "react-native-progress-steps", 49 | "react-native-step-by-step-process" 50 | ], 51 | "author": "Harshil Miyani", 52 | "license": "MIT", 53 | "peerDependencies": { 54 | "react": "18.1.0", 55 | "react-native": "0.70.3" 56 | }, 57 | "dependencies": { 58 | "react-native-responsive-fontsize": "^0.5.1" 59 | }, 60 | "devDependencies": { 61 | "metro-react-native-babel-preset": "^0.75.0", 62 | "@types/node": "^18.13.0", 63 | "@types/react": "^18.0.27", 64 | "@types/react-native": "^0.71.2", 65 | "rollup": "^3.15.0", 66 | "rollup-plugin-dts": "^5.2.0", 67 | "@rollup/plugin-commonjs": "^24.0.1", 68 | "@rollup/plugin-node-resolve": "^15.0.1", 69 | "tslib": "^2.5.0", 70 | "@rollup/plugin-typescript": "^11.0.0", 71 | "typescript": "^4.9.5" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/components/ProcessFooter.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from "react"; 2 | import { View, StyleSheet, TextStyle, ViewStyle } from "react-native"; 3 | import ProgressButton from "./UI/ProgressButton"; 4 | 5 | export interface ProcessFooterProps { 6 | removeButtonRow?: boolean; 7 | hidePreviousButton?: boolean | undefined; 8 | previousButtonStyle?: ViewStyle; 9 | previousButtonText?: string; 10 | previousButtonDisabled?: boolean; 11 | onPreviousStep?: () => {}; 12 | previousButtonTextStyle?: TextStyle; 13 | hideNextButton?: boolean | undefined; 14 | nextButtonStyle?: ViewStyle; 15 | nextButtonTitle?: string; 16 | onNextStep?: () => {}; 17 | nextButtonTextStyle?: TextStyle; 18 | nextButtonDisabled?: boolean; 19 | footerComponent?: ReactNode; 20 | } 21 | const ProcessFooter = ({ 22 | removeButtonRow, 23 | hidePreviousButton, 24 | previousButtonStyle, 25 | previousButtonText, 26 | previousButtonDisabled, 27 | onPreviousStep, 28 | previousButtonTextStyle, 29 | hideNextButton, 30 | nextButtonStyle, 31 | nextButtonTitle, 32 | onNextStep, 33 | nextButtonTextStyle, 34 | nextButtonDisabled, 35 | footerComponent, 36 | }: ProcessFooterProps) => { 37 | return removeButtonRow ? ( 38 | 39 | 40 | {!footerComponent && ( 41 | 42 | {hidePreviousButton ? ( 43 | 50 | ) : ( 51 | 52 | )} 53 | 54 | {hideNextButton && ( 55 | 62 | )} 63 | 64 | 65 | )} 66 | 67 | {footerComponent && ( 68 | {footerComponent} 69 | )} 70 | 71 | 72 | ) : null; 73 | }; 74 | 75 | export default ProcessFooter; 76 | 77 | const styles = StyleSheet.create({ 78 | footerContainer: { 79 | overflow: "hidden", 80 | backgroundColor: "transparent", 81 | paddingTop: "2%", 82 | height: "10%", 83 | minHeight: 70, 84 | maxHeight: 100, 85 | }, 86 | footerNestedContainer: { 87 | flex: 1, 88 | shadowColor: "#000", 89 | shadowOffset: { 90 | width: 0, 91 | height: 3, 92 | }, 93 | shadowOpacity: 0.27, 94 | shadowRadius: 4.65, 95 | 96 | elevation: 6, 97 | }, 98 | footerComponentContainer: { 99 | flex: 1, 100 | backgroundColor: "white", 101 | }, 102 | footerBtnContainer: { 103 | flexDirection: "row", 104 | justifyContent: "space-between", 105 | alignItems: "center", 106 | paddingHorizontal: "10%", 107 | flex: 1, 108 | backgroundColor: "white", 109 | }, 110 | }); 111 | -------------------------------------------------------------------------------- /src/components/StepIcon.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-native/no-inline-styles */ 2 | import React, { useContext } from "react"; 3 | import { 4 | View, 5 | Text, 6 | StyleSheet, 7 | useWindowDimensions, 8 | TextStyle, 9 | StyleProp, 10 | ViewStyle, 11 | Platform, 12 | } from "react-native"; 13 | import { RFValue } from "react-native-responsive-fontsize"; 14 | import { ProcessContext } from "../context/ProcessContextProvider"; 15 | export interface StepIconProps { 16 | label?: string; 17 | labelStyle?: TextStyle; 18 | stepNumber?: number; 19 | isActiveStep?: boolean; 20 | isCompletedStep?: boolean; 21 | completedStepIconColor?: string; 22 | activeStepIconColor?: string; 23 | activeStepNumColor?: string; 24 | disabledStepNumColor?: string; 25 | completedCheckColor?: string; 26 | showLabelAboveSteps?: boolean; 27 | } 28 | 29 | const StepIcon = ({ 30 | label, 31 | labelStyle, 32 | stepNumber, 33 | isActiveStep, 34 | isCompletedStep, 35 | completedStepIconColor = "#4BB543", 36 | activeStepIconColor = "#87CEEB", 37 | activeStepNumColor = "black", 38 | disabledStepNumColor = "white", 39 | completedCheckColor = "white", 40 | showLabelAboveSteps = false, 41 | }: StepIconProps) => { 42 | const { width } = useWindowDimensions(); 43 | 44 | const { totalSteps } = useContext(ProcessContext); 45 | 46 | let stylesConfig: { 47 | circleStyle?: StyleProp; 48 | stepNum?: StyleProp; 49 | } = {}; 50 | 51 | if (isActiveStep) { 52 | stylesConfig = { 53 | circleStyle: { 54 | backgroundColor: activeStepIconColor, 55 | shadowColor: activeStepIconColor, 56 | shadowOffset: { 57 | width: 0, 58 | height: 0, 59 | }, 60 | shadowOpacity: 0.5, 61 | shadowRadius: 4.65, 62 | 63 | elevation: 6, 64 | }, 65 | stepNum: { 66 | color: activeStepNumColor, 67 | fontSize: RFValue(12), 68 | }, 69 | }; 70 | } else if (isCompletedStep) { 71 | stylesConfig = { 72 | circleStyle: { 73 | backgroundColor: completedStepIconColor, 74 | }, 75 | }; 76 | } else { 77 | stylesConfig = { 78 | circleStyle: { 79 | backgroundColor: "lightgray", 80 | }, 81 | stepNum: { 82 | color: disabledStepNumColor, 83 | fontSize: RFValue(12), 84 | }, 85 | }; 86 | } 87 | 88 | return ( 89 | 90 | 91 | {showLabelAboveSteps && ( 92 | 104 | 8 ? width / 7 - 4 : width / totalSteps - 4, 111 | color: isActiveStep 112 | ? labelStyle?.color 113 | ? labelStyle?.color 114 | : "black" 115 | : "transparent", 116 | }, 117 | ]} 118 | numberOfLines={2} 119 | minimumFontScale={0.9} 120 | adjustsFontSizeToFit={Platform.OS === "ios" ? true : false} 121 | > 122 | {label} 123 | 124 | 125 | )} 126 | 134 | 148 | 149 | {isCompletedStep ? ( 150 | 151 | ) : ( 152 | {stepNumber} 153 | )} 154 | 155 | 156 | 157 | {!showLabelAboveSteps && ( 158 | 169 | 8 ? width / 7 - 4 : width / totalSteps - 4, 176 | color: isActiveStep 177 | ? labelStyle?.color 178 | ? labelStyle?.color 179 | : "black" 180 | : "transparent", 181 | }, 182 | ]} 183 | numberOfLines={2} 184 | minimumFontScale={0.9} 185 | adjustsFontSizeToFit={Platform.OS === "ios" ? true : false} 186 | > 187 | {label} 188 | 189 | 190 | )} 191 | 192 | 193 | ); 194 | }; 195 | 196 | export default StepIcon; 197 | 198 | const styles = StyleSheet.create({ 199 | stepIconContainer: { 200 | width: "100%", 201 | height: "100%", 202 | justifyContent: "center", 203 | alignItems: "center", 204 | }, 205 | stepIconInnerContainer: { 206 | flex: 1, 207 | width: "100%", 208 | height: "100%", 209 | justifyContent: "center", 210 | alignItems: "center", 211 | }, 212 | stepIconCircleContainer: { 213 | width: "100%", 214 | justifyContent: "center", 215 | alignItems: "center", 216 | maxHeight: 60, 217 | maxWidth: 60, 218 | }, 219 | labelContainer: { 220 | width: "100%", 221 | }, 222 | label: { 223 | textAlign: "center", 224 | flex: 1, 225 | fontWeight: "bold", 226 | fontSize: RFValue(12), 227 | }, 228 | circleText: { 229 | alignSelf: "center", 230 | fontWeight: "bold", 231 | }, 232 | }); 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://img.shields.io/npm/v/react-native-step-by-step-process.svg?style=flat) 2 | ![](https://img.shields.io/npm/dt/react-native-step-by-step-process.svg) 3 | 4 | # react-native-step-by-step-process 5 | 6 | A simple and fully customizable & fully adaptive UI with `Android`, `iOS`, `iPad` React Native component that implements a process steps UI. 7 | 8 | - Unlimitaed Stpes Support. 9 | - Each steps content is displayed inside of a Stps view with lable. 10 | - Fully customizable buttons are displayed at the bottom of the component to navigate between steps. 11 | - Provided `useProcess` Hook will help to get & set current step indexs & more. 12 | - Constantly bug fixing and improvement supported & will support continuously. 13 | 14 | | iOS | Android | 15 | | :-----------------: | :-----: | 16 | | ![](assets/iOS.gif) | ![]() | 17 | 18 | ## Installation 19 | 20 | If using yarn: 21 | 22 | ``` 23 | yarn add react-native-step-by-step-process 24 | ``` 25 | 26 | If using npm: 27 | 28 | ``` 29 | npm i react-native-step-by-step-process 30 | ``` 31 | 32 | ## Usage 33 | 34 | ``` 35 | import { ProcessContainer, createProcess, useProcess} from 'react-native-step-by-step-process'; 36 | ``` 37 | 38 | First create Screen Component which will look like below and wrrap that with `ProcessContainer` and pass initial Number Of Steps to `initialNumberOfSteps`. 39 | 40 | ``` 41 | import React from 'react'; 42 | import {ProcessContainer} from 'react-native-step-by-step-process'; 43 | 44 | import ProcessStepsCompoents from '../components/ProcessStepsCompoents'; 45 | 46 | const ProcessStepsScreen = () => { 47 | return ( 48 | 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default ProcessStepsScreen; 55 | ``` 56 | 57 | Simply create process using `createProcess` method which will return `Process` Object and using that `Process` Object, place a `` tag for each desired step within the `` wrapper. 58 | 59 | ``` 60 | import React from "react"; 61 | import { Text, View } from "react-native"; 62 | import {createProcess, useProcess} from 'react-native-step-by-step-process'; 63 | 64 | const Process = createProcess(); 65 | 66 | const ProcessStepsCompoents = () => { 67 | const {currentStep, activeStep} = useProcess(); 68 | 69 | return ( 70 | 71 | 72 | 73 | 74 | Step 1! 75 | 76 | 77 | 78 | 79 | Step 2! 80 | 81 | 82 | 83 | 84 | Step 3! 85 | 86 | 87 | 88 | 89 | Step 4! 90 | 91 | 92 | 93 | 94 | Step 4! 95 | 96 | 97 | 98 | 99 | ); 100 | }; 101 | 102 | export default ProcessStepsCompoents; 103 | ``` 104 | 105 | ### Current Step Error and Validation Handling 106 | 107 | The `errors` should be used if there's a need for validation and error handling when clicking the next button. If you would like to prevent the next step from being rendered, Just `return` `true` or `false` from `onNext` function and `onPrevious` function. See below example below : 108 | 109 | Example usage of validation check: 110 | 111 | ``` 112 | import React from "react"; 113 | import { Text, View } from "react-native"; 114 | import {createProcess, useProcess} from 'react-native-step-by-step-process'; 115 | 116 | const Process = createProcess(); 117 | 118 | const ProcessStepsScreen = () => { 119 | const {currentStep, activeStep} = useProcess(); 120 | 121 | const onPressNextInStep = (nextStepIndex) => { 122 | if(nextStepIndex === 2){ 123 | return true; // This will prevent the next step from being rendered 124 | } 125 | } 126 | 127 | return ( 128 | 138 | 141 | 142 | Step 1! 143 | 144 | 145 | 146 | Step 2! 147 | 148 | 149 | 150 | Step 3! 151 | 152 | 153 | 154 | Step 4! 155 | 156 | 157 | 158 | ); 159 | }; 160 | 161 | export default ProcessStepsScreen; 162 | ``` 163 | 164 | ## Documentation 165 | 166 | ### process Steps Component 167 | 168 | | Name | Description | Default | Type | 169 | | ---------------------- | ------------------------------------- | ----------- | ------- | 170 | | activeStepIconColor | Color of the active step icon | transparent | String | 171 | | completedStepIconColor | Color of the completed step icon | #4bb543 | String | 172 | | activeStepNumColor | Color of the active step number | black | String | 173 | | disabledStepNumColor | Color of the disabled step number | white | String | 174 | | completedCheckColor | Color of the completed step checkmark | white | String | 175 | | isComplete | Set all Steps to active state | false | Boolean | 176 | 177 | ### process Step Component 178 | 179 | | Name | Description | Default | Type | 180 | | --------------------------- | -------------------------------------------------------- | ----------------------------------- | ------- | 181 | | label | Title of the current step to be displayed | null | String | 182 | | onNext | Function called when the next step button is pressed | null | Func | 183 | | onPrevious | Function called when the previous step button is pressed | null | Func | 184 | | onSubmit | Function called when the submit step button is pressed | null | Func | 185 | | nextButtonText | Text to display inside the next button | Next | String | 186 | | previousButtonText | Text to display inside the previous button | Previous | String | 187 | | finishButtonText | Text to display inside the button on the last step | Submit | String | 188 | | nextButtonStyle | Style object to provide to the next/finish buttons | { textAlign: 'center', padding: 8 } | Object | 189 | | nextButtonTextStyle | Style object to provide to the next/finish button text | { color: '#007aff', fontSize: 18 } | Object | 190 | | nextButtonDisabled | Value to disable/enable next button | false | Boolean | 191 | | previousButtonStyle | Style object to provide to the previous button | { textAlign: 'center', padding: 8 } | Object | 192 | | previousButtonTextStyle | Style object to provide to the previous button text | { color: '#007aff', fontSize: 18 } | Object | 193 | | previousButtonDisabled | Value to disable/enable previous button | false | Boolean | 194 | | removeButtonRow | Used to render the process step without the button row | false | Boolean | 195 | | showFirstStepPreviousButton | Used to render the previous button in first step | false | Boolean | 196 | 197 | ## Contributing 198 | 199 | Pull requests are always welcome! Feel free to open a new GitHub issue for any changes that can be made. 200 | 201 | ## Author 202 | 203 | [Harshil Miyani](https://github.com/harshilmiyani) 204 | 205 | ## License 206 | 207 | MIT 208 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true /* Enable constraints that allow a TypeScript project to be used with project references. */, 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | // "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | // "lib": [ 16 | // "esnext", 17 | // "dom" 18 | // ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, 19 | // "jsx": "react" /* Specify what JSX code is generated. */, 20 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 21 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 22 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 23 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 24 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 25 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 26 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 27 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 28 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 29 | 30 | /* Modules */ 31 | // "module": "ESNext" /* Specify what module code is generated. */, 32 | "rootDir": "src", /* Specify the root folder within your source files. */ 33 | // "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, 34 | "baseUrl": "." /* Specify the base directory to resolve non-relative module names. */, 35 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 36 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 37 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 38 | // "types": [ 39 | // "node" 40 | // ] /* Specify type package names to be included without being referenced in a source file. */, 41 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 42 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 43 | // "resolveJsonModule": true, /* Enable importing .json files. */ 44 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 45 | 46 | /* JavaScript Support */ 47 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 48 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 49 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 50 | 51 | /* Emit */ 52 | // "declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */, 53 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 54 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 55 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 56 | // "outFile": "" /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */, 57 | "outDir": "dist" /* Specify an output folder for all emitted files. */, 58 | // "removeComments": true, /* Disable emitting comments. */ 59 | // "noEmit": true, /* Disable emitting files from a compilation. */ 60 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 61 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 62 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 63 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 64 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 65 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 66 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 67 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 68 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 69 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 70 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 71 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 72 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 73 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 74 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 75 | 76 | /* Interop Constraints */ 77 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 78 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 79 | // "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 80 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 81 | // "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 82 | 83 | /* Type Checking */ 84 | // "strict": true /* Enable all strict type-checking options. */, 85 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 86 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 87 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 88 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 89 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 90 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 91 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 92 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 93 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 94 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 95 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 96 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 97 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 98 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 99 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 100 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 101 | // "allowUnusedLabels": false /* Disable error reporting for unused labels. */, 102 | // "allowUnreachableCode": false /* Disable error reporting for unreachable code. */, 103 | 104 | /* Completeness */ 105 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 106 | // "skipLibCheck": true /* Skip type checking all .d.ts files. */, 107 | 108 | // Default 109 | "target": "ESNext", 110 | "esModuleInterop": true, 111 | "forceConsistentCasingInFileNames": true, 112 | "strict": true, 113 | "skipLibCheck": true, 114 | 115 | // Added 116 | "jsx": "react", 117 | "module": "ESNext", 118 | "declaration": true, 119 | "declarationDir": "types", 120 | "sourceMap": true, 121 | "moduleResolution": "node", 122 | "allowSyntheticDefaultImports": true, 123 | "emitDeclarationOnly": true 124 | }, 125 | "include": ["src/**/*.ts", "src/**/*.tsx"], 126 | "exclude": ["node_modules"], 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/components/ProcessFlow.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-native/no-inline-styles */ 2 | import React, { 3 | isValidElement, 4 | useEffect, 5 | useState, 6 | useContext, 7 | ReactNode, 8 | ReactElement, 9 | useMemo, 10 | } from "react"; 11 | import { 12 | LayoutRectangle, 13 | SafeAreaView, 14 | ScrollView, 15 | StyleSheet, 16 | useWindowDimensions, 17 | View, 18 | } from "react-native"; 19 | import ProcessFooter, { ProcessFooterProps } from "./ProcessFooter"; 20 | import { ProcessContext } from "../context/ProcessContextProvider"; 21 | import { StepProps } from "./Step"; 22 | import StepIcon, { StepIconProps } from "./StepIcon"; 23 | 24 | export interface ProcessFlowProps 25 | extends StepProps, 26 | ProcessFooterProps, 27 | StepIconProps { 28 | // isComplete not perfectly working now 29 | // isComplete = false, 30 | children: ReactElement[]; 31 | 32 | onChangeStep?: ( 33 | currentStepIndex?: number, 34 | nextStepIndex?: number, 35 | previousStepIndex?: number 36 | ) => {} | undefined; 37 | 38 | // Buttons Config 39 | onPrevious: (previousStepIndex: number) => {}; 40 | onNext: (nextStepIndex: number) => {}; 41 | onSubmit?: () => {}; 42 | nextButtonText?: string; 43 | previousButtonText?: string; 44 | finishButtonText?: string; 45 | nextButtonDisabled?: boolean; 46 | previousButtonDisabled?: boolean; 47 | removeButtonRow?: boolean; 48 | footerComponent?: ReactNode; 49 | showLabelAboveSteps?: boolean; 50 | } 51 | 52 | const ProcessFlow = ({ 53 | // isComplete not perfectly working now 54 | // isComplete = false, 55 | children, 56 | 57 | onChangeStep, 58 | 59 | // Buttons Config 60 | onNext, 61 | onPrevious, 62 | onSubmit, 63 | nextButtonStyle, 64 | nextButtonText = "Next", 65 | nextButtonTextStyle, 66 | previousButtonStyle, 67 | previousButtonText = "Previous", 68 | previousButtonTextStyle, 69 | finishButtonText = "Submit", 70 | nextButtonDisabled = false, 71 | previousButtonDisabled = false, 72 | removeButtonRow = false, 73 | footerComponent, 74 | labelStyle, 75 | showLabelAboveSteps = false, 76 | ...props 77 | }: ProcessFlowProps) => { 78 | const { activeStep, currentStep, numberOfSteps, totalSteps } = 79 | useContext(ProcessContext); 80 | const { width } = useWindowDimensions(); 81 | const [stepIconWidthOffSet, setStepIconWidthOffSet] = useState< 82 | Array<{ 83 | layout: LayoutRectangle; 84 | target?: number | null | undefined; 85 | }> 86 | >([]); 87 | 88 | useEffect(() => { 89 | if (activeStep >= 0 && activeStep < totalSteps) { 90 | if (onChangeStep) { 91 | onChangeStep(activeStep, activeStep + 1, activeStep - 1); 92 | } 93 | } 94 | }, [onChangeStep, activeStep, totalSteps]); 95 | 96 | // useEffect(() => { 97 | // if (isComplete) { 98 | // currentStep(totalSteps - 1); 99 | // } 100 | // }, [currentStep, isComplete, totalSteps]); 101 | 102 | const renderChildren = React.useMemo(() => { 103 | const tmp = React.Children.map(children, (ele) => { 104 | if (ele && React.isValidElement(ele) && !ele?.props?.hide) { 105 | return React.cloneElement(ele); 106 | } 107 | }); 108 | numberOfSteps(tmp?.length); 109 | return tmp; 110 | }, [children, numberOfSteps]); 111 | 112 | const renderStepIcons = () => { 113 | let step = []; 114 | let i = 0; 115 | 116 | while (i !== totalSteps) { 117 | const isCompletedStep = i < activeStep; 118 | const isActiveStep = i === activeStep; 119 | step.push( 120 | { 124 | setStepIconWidthOffSet((s) => { 125 | return [...s, offSet].slice(-totalSteps).sort((a, b) => { 126 | return a.layout.x - b.layout.x; 127 | }); 128 | }); 129 | }} 130 | > 131 | 145 | 146 | ); 147 | i++; 148 | } 149 | return step; 150 | }; 151 | 152 | const onNextStep = async () => { 153 | const isThereError = onNext && !!(await onNext(activeStep + 1)); 154 | const isError = 155 | renderChildren[activeStep]?.props?.onNext && 156 | !!(await renderChildren[activeStep]?.props?.onNext(activeStep + 1)); 157 | // Return out of method before moving to next step if errors exist. 158 | if (isThereError || isError) { 159 | return; 160 | } 161 | currentStep(activeStep + 1); 162 | }; 163 | 164 | const onPreviousStep = async () => { 165 | const isThereError = onPrevious && !!(await onPrevious(activeStep + 1)); 166 | const isError = 167 | renderChildren[activeStep]?.props?.onPrevious && 168 | !!(await renderChildren[activeStep]?.props?.onPrevious(activeStep + 1)); 169 | 170 | if (isThereError || isError) { 171 | return; 172 | } 173 | // Changes active index and calls previous function passed by parent 174 | if (activeStep >= 0) { 175 | currentStep(activeStep - 1); 176 | } 177 | }; 178 | 179 | const onSubmitHandler = async () => { 180 | onSubmit && onSubmit(); 181 | }; 182 | 183 | const indicatorTop = useMemo( 184 | () => 185 | showLabelAboveSteps 186 | ? (width * 10) / 100 >= 50 187 | ? 77 188 | : (width * 27) / 100 / 2 189 | : (width * 10) / 100 >= 50 190 | ? 30 191 | : (width * 10) / 100 / 2, 192 | [width, showLabelAboveSteps] 193 | ); 194 | 195 | return ( 196 | 197 | 203 | {totalSteps < 9 && ( 204 | 216 | {renderStepIcons()} 217 | 226 | 235 | 236 | 237 | )} 238 | {totalSteps >= 9 && ( 239 | 261 | {renderStepIcons()} 262 | 272 | 281 | 282 | 283 | )} 284 | 285 | 286 | {isValidElement(renderChildren[activeStep]) && 287 | React.cloneElement(renderChildren[activeStep])} 288 | 289 | 290 | 295 | {footerComponent && footerComponent} 296 | {renderChildren[activeStep]?.props?.footerComponent && 297 | renderChildren[activeStep]?.props?.footerComponent} 298 | 299 | ) : undefined 300 | } 301 | removeButtonRow={ 302 | !removeButtonRow && 303 | !renderChildren[activeStep]?.props?.removeButtonRow 304 | } 305 | hidePreviousButton={ 306 | !renderChildren[activeStep]?.props?.hidePreviousButton && 307 | (!(activeStep === 0) || 308 | renderChildren[activeStep]?.props?.showFirstStepPreviousButton) 309 | } 310 | previousButtonStyle={previousButtonStyle} 311 | previousButtonText={ 312 | previousButtonText 313 | ? renderChildren[activeStep]?.props?.previousButtonText 314 | ? renderChildren[activeStep]?.props?.previousButtonText 315 | : previousButtonText 316 | : "" 317 | } 318 | previousButtonDisabled={previousButtonDisabled} 319 | onPreviousStep={onPreviousStep} 320 | previousButtonTextStyle={previousButtonTextStyle} 321 | hideNextButton={!renderChildren[activeStep]?.props?.hideNextButton} 322 | nextButtonStyle={nextButtonStyle} 323 | nextButtonTitle={ 324 | activeStep === totalSteps - 1 325 | ? finishButtonText 326 | : renderChildren[activeStep]?.props?.nextButtonText 327 | ? renderChildren[activeStep]?.props?.nextButtonText 328 | : nextButtonText 329 | } 330 | onNextStep={ 331 | activeStep === totalSteps - 1 ? onSubmitHandler : onNextStep 332 | } 333 | nextButtonTextStyle={nextButtonTextStyle} 334 | nextButtonDisabled={nextButtonDisabled} 335 | /> 336 | 337 | ); 338 | }; 339 | 340 | export default ProcessFlow; 341 | 342 | const styles = StyleSheet.create({ 343 | container: { 344 | flex: 1, 345 | }, 346 | stepIndicatorOuter: { 347 | zIndex: -1, 348 | backgroundColor: "lightgray", 349 | position: "absolute", 350 | alignSelf: "center", 351 | }, 352 | stepIndicatorInner: { 353 | borderColor: "#4BB543", 354 | }, 355 | bodyContainer: { 356 | flex: 1, 357 | }, 358 | stepIcons: { 359 | flexDirection: "row", 360 | justifyContent: "center", 361 | alignItems: "center", 362 | alignSelf: "center", 363 | backgroundColor: "white", 364 | }, 365 | }); 366 | --------------------------------------------------------------------------------