├── .babelrc ├── .github └── workflows │ └── stale-and-close.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── assets └── example.png ├── examples └── example.jsx ├── package-lock.json ├── package.json ├── src ├── ProgressSteps │ ├── ProgressStep.tsx │ ├── ProgressSteps.tsx │ └── StepIcon.tsx ├── index.ts └── types.ts └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["module:metro-react-native-babel-preset"] 3 | } 4 | -------------------------------------------------------------------------------- /.github/workflows/stale-and-close.yml: -------------------------------------------------------------------------------- 1 | # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. 2 | # 3 | # You can adjust the behavior by modifying this file. 4 | # For more information, see: 5 | # https://github.com/actions/stale 6 | name: Mark stale issues and pull requests then close 7 | 8 | on: 9 | schedule: 10 | - cron: '0 8 * * *' 11 | workflow_dispatch: 12 | 13 | jobs: 14 | stale: 15 | 16 | runs-on: ubuntu-latest 17 | permissions: 18 | issues: write 19 | pull-requests: write 20 | 21 | steps: 22 | - uses: actions/stale@v9 23 | with: 24 | close-issue-message: 'This issue is being automatically closed. The big release of v2 is finally here and addresses a lot of outstanding issues! If your problem still persists, please open another issue and we will take a look. Thanks for using the package!' 25 | close-pr-message: 'This PR is being automatically closed. The big release of v2 is finally here and a lot has changed! If you feel the PR is still relevant to the new package version, please open another and we will take a look. Thanks for contributing to the project!' 26 | days-before-close: 0 27 | operations-per-run: 75 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log 4 | 5 | # Runtime data 6 | tmp 7 | build 8 | dist 9 | 10 | # Dependency directory 11 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | *.log 3 | npm-debug.log 4 | 5 | # Dependency directory 6 | node_modules 7 | 8 | # Runtime data 9 | tmp 10 | 11 | # Examples 12 | examples -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Colby Miller 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://img.shields.io/npm/v/react-native-progress-steps.svg) 2 | ![](https://img.shields.io/npm/dm/react-native-progress-steps) 3 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com) 4 | 5 | # React Native Progress Steps 6 | 7 | A simple and fully customizable React Native component that implements a progress stepper UI. 8 | 9 | - Each steps content is displayed inside of a customizable ScrollView. 10 | - Customizable buttons are displayed at the bottom of the component to move between steps. 11 | 12 | ## ✨ What's New in v2.0 13 | 14 | - 🎯 **Full TypeScript Support** - Complete type definitions for an enhanced development experience 15 | - 🔄 **Modern Component Architecture** - Refactored to use functional components and React hooks 16 | - 🎨 **Major UI/UX Improvements** 17 | - Enhanced responsiveness and layout 18 | - Modernized styling with new step icons, default colors, and button design 19 | - Improved performance 20 | - Better readability 21 | - 💫 **Smooth Step Transitions** - Added subtle animations when changing between steps 22 | - 🛠️ **Enhanced Customization** - Streamlined props with new customization options and removal of legacy features 23 | - ⛔️ **Breaking Changes** - Some props have been removed and renamed. See the [Migration Guide](#migration-guide-v1-to-v2) for more details. 24 | 25 | ## Example 26 | 27 | 29 | 30 | examples/example.jsx 31 | 32 | ## Installation 33 | 34 | ``` 35 | npm i react-native-progress-steps 36 | ``` 37 | 38 | ## Usage 39 | 40 | ``` 41 | import { ProgressSteps, ProgressStep } from 'react-native-progress-steps'; 42 | ``` 43 | 44 | Simply place a `` tag for each desired step within the `` wrapper. 45 | 46 | ``` 47 | 48 | 49 | 50 | 51 | This is the content within step 1! 52 | 53 | 54 | 55 | 56 | This is the content within step 2! 57 | 58 | 59 | 60 | 61 | This is the content within step 3! 62 | 63 | 64 | 65 | 66 | ``` 67 | 68 | ### Button Styling Usage 69 | 70 | Navigation buttons are customizable using the various props provided to the `ProgressStep` component. See the [Progress Step Component](#progress-step-component) section for more details. 71 | 72 | Simple example to set a specific button text: 73 | 74 | ``` 75 | return ( 76 | 77 | 78 | 79 | 80 | This is the content within step 1! 81 | 82 | 83 | 84 | 85 | This is the content within step 2! 86 | 87 | 88 | 89 | 90 | ) 91 | ``` 92 | 93 | ### Hiding the Button Row 94 | 95 | To hide the button row, set the `removeBtnRow` prop to `true`. The current step can be changed without the buttons by updating and managing the `activeStep` prop on the `` component. 96 | 97 | ``` 98 | const [activeStep, setActiveStep] = useState(0); 99 | 100 | 101 | 102 | 103 | This is the content within step 1! 104 | 105 | 106 | 107 | ``` 108 | 109 | ### Current Step Error and Validation Handling 110 | 111 | The `errors` prop 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, set the `errors` prop to `true`. By default, it will be `false`. 112 | 113 | Example usage of validation check: 114 | 115 | ``` 116 | const [isValid, setIsValid] = useState(false); 117 | const [errors, setErrors] = useState(false); 118 | 119 | const onNextStep = () => { 120 | if (!isValid) { 121 | setErrors(true); 122 | } else { 123 | setErrors(false); 124 | } 125 | }; 126 | 127 | return ( 128 | 129 | 130 | 131 | 132 | This is the content within step 1! 133 | 134 | 135 | 136 | 137 | This is the content within step 2! 138 | 139 | 140 | 141 | 142 | ); 143 | ``` 144 | 145 | ## Documentation 146 | 147 | ### Progress Steps Component 148 | 149 | | Name | Description | Default | Type | 150 | | ------------------------- | ------------------------------------------------- | -------------- | ------- | 151 | | borderWidth | Width of the progress bar between steps | 2 | Number | 152 | | activeStepIconBorderColor | Outside border color for the active step | #2D2D2D | String | 153 | | progressBarColor | Color of the default progress bar | #EBEBE4 | String | 154 | | completedProgressBarColor | Color of the completed progress bar | #2D2D2D | String | 155 | | activeStepIconColor | Color of the active step icon | transparent | String | 156 | | completedStepIconColor | Color of the completed step icon | #2D2D2D | String | 157 | | disabledStepIconColor | Color of the disabled step icon | #EBEBE4 | String | 158 | | labelFontFamily | Font family for the step icon label | System default | String | 159 | | labelColor | Color of the default label | #D3D3D3 | String | 160 | | labelFontSize | Font size for the step icon label | 14 | Number | 161 | | activeLabelColor | Color of the active label | #2D2D2D | String | 162 | | activeLabelFontSize | Optional font size for the active step icon label | 14 | Number | 163 | | completedLabelColor | Color of the completed label | #2D2D2D | String | 164 | | activeStepNumColor | Color of the active step number | #2D2D2D | String | 165 | | completedStepNumColor | Color of the completed step number | #2D2D2D | String | 166 | | disabledStepNumColor | Color of the disabled step number | #FFFFFF | String | 167 | | completedCheckColor | Color of the completed step checkmark | #FFFFFF | String | 168 | | activeStep | Manually specify the active step | 0 | Number | 169 | | isComplete | Set all Steps to active state | false | Boolean | 170 | | topOffset | Set progress bar top offset | 60 | Number | 171 | | marginBottom | Set progress bar bottom margin | 30 | Number | 172 | 173 | ### Progress Step Component 174 | 175 | | Name | Description | Default | Type | 176 | | ----------------------- | ------------------------------------------------------------------------------------- | --------- | ------- | 177 | | label | Title of the current step to be displayed | undefined | String | 178 | | onNext | Function called when the next step button is pressed | undefined | Func | 179 | | onPrevious | Function called when the previous step button is pressed | undefined | Func | 180 | | onSubmit | Function called when the submit step button is pressed | undefined | Func | 181 | | scrollViewProps | Object to provide props to ScrollView component | undefined | Object | 182 | | scrollable | The content of the Progress Step should be scrollable | true | Boolean | 183 | | viewProps | Object to provide props to view component if scrollable is false | undefined | Object | 184 | | errors | Used to assist with current step validation. If true, the next step won't be rendered | false | Boolean | 185 | | removeBtnRow | Used to render the progress step without the button row | false | Boolean | 186 | | buttonNextText | Text to display inside the next button | Next | String | 187 | | buttonPreviousText | Text to display inside the previous button | Previous | String | 188 | | buttonFinishText | Text to display inside the button on the last step | Submit | String | 189 | | buttonNextDisabled | Value to disable/enable next button | false | Boolean | 190 | | buttonPreviousDisabled | Value to disable/enable previous button | false | Boolean | 191 | | buttonFinishDisabled | Value to disable/enable finish button | false | Boolean | 192 | | buttonTopOffset | Set button row top offset | 12 | Number | 193 | | buttonBottomOffset | Set button row bottom offset | 20 | Number | 194 | | buttonHorizontalOffset | Set button row horizontal offset | 30 | Number | 195 | | buttonFillColor | Background color for the next/finish buttons | #2D2D2D | String | 196 | | buttonBorderColor | Border color for the previous button | #2D2D2D | String | 197 | | buttonNextTextColor | Text color for the next button | #FFFFFF | String | 198 | | buttonPreviousTextColor | Text color for the previous button | #2D2D2D | String | 199 | | buttonFinishTextColor | Text color for the finish button | #FFFFFF | String | 200 | | buttonDisabledColor | Background color for disabled buttons | #CDCDCD | String | 201 | | buttonDisabledTextColor | Text color for disabled buttons | #FFFFFF | String | 202 | 203 | ## Migration Guide (v1 to v2) 204 | 205 | ### Breaking Changes 206 | 207 | #### Renamed Props 208 | 209 | The following props have been renamed for better clarity and consistency: 210 | 211 | | Old Prop Name | New Prop Name | Component | 212 | | ------------------- | ---------------------- | ------------ | 213 | | nextBtnText | buttonNextText | ProgressStep | 214 | | previousBtnText | buttonPreviousText | ProgressStep | 215 | | finishBtnText | buttonFinishText | ProgressStep | 216 | | nextBtnDisabled | buttonNextDisabled | ProgressStep | 217 | | previousBtnDisabled | buttonPreviousDisabled | ProgressStep | 218 | 219 | #### Removed Props 220 | 221 | The following props have been removed in favor of the new styling system: 222 | 223 | | Removed Prop | Alternative | 224 | | -------------------- | ------------------------------------------------------ | 225 | | nextBtnStyle | Use `buttonFillColor` and other button styling props | 226 | | nextBtnTextStyle | Use `buttonNextTextColor` instead | 227 | | previousBtnStyle | Use `buttonBorderColor` and other button styling props | 228 | | previousBtnTextStyle | Use `buttonPreviousTextColor` instead | 229 | | finishBtnStyle | Use `buttonFillColor` and other button styling props | 230 | | finishBtnTextStyle | Use `buttonFinishTextColor` instead | 231 | | borderStyle | No longer supported | 232 | 233 | #### New Button Styling System 234 | 235 | Instead of using style objects, the new version provides specific props for common button customizations: 236 | 237 | ``` 238 | // Old way 239 | 243 | {/* content */} 244 | 245 | 246 | // New way 247 | 251 | {/* content */} 252 | 253 | ``` 254 | 255 | See the [Progress Step Component](#progress-step-component) section for all available button styling props. 256 | 257 | ## Contributing 258 | 259 | Pull requests are always welcome! Feel free to open a new GitHub issue for any changes that can be made. 260 | 261 | **Working on your first Pull Request?** You can learn how from GitHub's [First Contributions](https://github.com/firstcontributions/first-contributions) guide or their [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) guide. 262 | 263 | ## Author 264 | 265 | Colby Miller | [https://colbymillerdev.com](https://colbymillerdev.com) 266 | 267 | ## License 268 | 269 | [MIT](./LICENSE) 270 | -------------------------------------------------------------------------------- /assets/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colbymillerdev/react-native-progress-steps/1d8b0e07bc147160fc9e237563afa6382ec1e1f8/assets/example.png -------------------------------------------------------------------------------- /examples/example.jsx: -------------------------------------------------------------------------------- 1 | import { View, Text, StyleProp, ViewStyle } from 'react-native'; 2 | import { ProgressStep, ProgressSteps } from 'react-native-progress-steps'; 3 | 4 | export default function ExampleProgressSteps() { 5 | const defaultScrollViewProps = { 6 | keyboardShouldPersistTaps: 'handled', 7 | contentContainerStyle: { 8 | flexGrow: 1, 9 | justifyContent: 'center', 10 | }, 11 | }; 12 | 13 | const onPaymentStepComplete = () => { 14 | console.log('payment step completed!'); 15 | }; 16 | 17 | const onNextStep = () => { 18 | console.log('called next step'); 19 | }; 20 | 21 | const onPrevStep = () => { 22 | console.log('called previous step'); 23 | }; 24 | 25 | const onSubmitSteps = () => { 26 | console.log('called submit step'); 27 | }; 28 | 29 | return ( 30 | 31 | 32 | 38 | 39 | Payment step content 40 | 41 | 42 | 48 | 49 | Shipping address step content 50 | 51 | 52 | 58 | 59 | Billing address step content 60 | 61 | 62 | 68 | 69 | Confirm order step content 70 | 71 | 72 | 73 | 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-progress-steps", 3 | "version": "2.0.3", 4 | "description": "A simple and fully customizable React Native component that implements a progress stepper UI.", 5 | "author": "Colby Miller", 6 | "license": "MIT", 7 | "main": "./dist/index.js", 8 | "types": "./dist/index.d.ts", 9 | "files": [ 10 | "dist", 11 | "src" 12 | ], 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\" && exit 1", 15 | "build": "tsc", 16 | "prepare": "npm run build" 17 | }, 18 | "dependencies": { 19 | "lodash": "^4.17.21" 20 | }, 21 | "devDependencies": { 22 | "@types/lodash": "^4.17.15", 23 | "@types/react": "^18.3.18", 24 | "metro-react-native-babel-preset": "^0.77.0", 25 | "typescript": "^5.7.3" 26 | }, 27 | "peerDependencies": { 28 | "react": ">=16.8.0", 29 | "react-native": ">=0.59.0", 30 | "lucide-react-native": ">=0.475.0" 31 | }, 32 | "keywords": [ 33 | "react-native", 34 | "react component", 35 | "react native component", 36 | "react", 37 | "react native", 38 | "mobile", 39 | "ios", 40 | "android", 41 | "ui", 42 | "stepper", 43 | "progress", 44 | "progress steps", 45 | "typescript" 46 | ], 47 | "repository": { 48 | "type": "git", 49 | "url": "git+https://github.com/colbymillerdev/react-native-progress-steps.git" 50 | }, 51 | "bugs": { 52 | "url": "https://github.com/colbymillerdev/react-native-progress-steps/issues" 53 | }, 54 | "homepage": "https://github.com/colbymillerdev/react-native-progress-steps#readme" 55 | } 56 | -------------------------------------------------------------------------------- /src/ProgressSteps/ProgressStep.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, ScrollView, Text, Pressable, ViewStyle, TextStyle } from 'react-native'; 3 | import type { ProgressStepProps } from '../types'; 4 | 5 | const ProgressStep = ({ 6 | errors = false, 7 | removeBtnRow = false, 8 | scrollable = true, 9 | activeStep = 0, 10 | stepCount = 0, 11 | buttonNextText = 'Next', 12 | buttonPreviousText = 'Previous', 13 | buttonFinishText = 'Submit', 14 | buttonNextDisabled = false, 15 | buttonPreviousDisabled = false, 16 | buttonFinishDisabled = false, 17 | buttonBottomOffset = 20, 18 | buttonTopOffset = 12, 19 | buttonHorizontalOffset = 30, 20 | buttonFillColor = '#2D2D2D', 21 | buttonNextTextColor = '#FFFFFF', 22 | buttonPreviousTextColor = '#2D2D2D', 23 | buttonFinishTextColor = '#FFFFFF', 24 | buttonBorderColor = '#2D2D2D', 25 | buttonDisabledColor = '#CDCDCD', 26 | buttonDisabledTextColor = '#FFFFFF', 27 | ...props 28 | }: ProgressStepProps) => { 29 | const isPreviousBtnHidden = activeStep === 0; 30 | const isFirstStep = activeStep === 0; 31 | 32 | const onNextStep = (): void => { 33 | props.onNext?.(); 34 | 35 | if (!errors && props.setActiveStep) { 36 | props.setActiveStep(activeStep + 1); 37 | } 38 | }; 39 | 40 | const onPreviousStep = (): void => { 41 | props.onPrevious?.(); 42 | 43 | if (props.setActiveStep) { 44 | props.setActiveStep(activeStep - 1); 45 | } 46 | }; 47 | 48 | const baseButtonStyle: ViewStyle = { 49 | height: 48, 50 | minWidth: 120, 51 | borderRadius: 6, 52 | justifyContent: 'center', 53 | alignItems: 'center', 54 | }; 55 | 56 | const baseTextStyle: TextStyle = { 57 | fontSize: 16, 58 | fontWeight: '500', 59 | }; 60 | 61 | const renderNextButton = (): JSX.Element => ( 62 | [ 64 | baseButtonStyle, 65 | { 66 | backgroundColor: buttonFillColor, 67 | opacity: pressed ? 0.8 : 1, 68 | }, 69 | buttonNextDisabled && { backgroundColor: buttonDisabledColor }, 70 | ]} 71 | onPress={onNextStep} 72 | disabled={buttonNextDisabled} 73 | > 74 | {buttonNextText} 75 | 76 | ); 77 | 78 | const renderPreviousButton = (): JSX.Element => ( 79 | [ 81 | baseButtonStyle, 82 | { 83 | borderWidth: 1, 84 | borderColor: buttonPreviousDisabled ? 'transparent' : buttonBorderColor, 85 | opacity: pressed ? 0.8 : 1, 86 | }, 87 | buttonPreviousDisabled && { backgroundColor: buttonDisabledColor }, 88 | ]} 89 | onPress={onPreviousStep} 90 | disabled={buttonPreviousDisabled} 91 | > 92 | 95 | {isFirstStep ? '' : buttonPreviousText} 96 | 97 | 98 | ); 99 | 100 | const renderSubmitButton = (): JSX.Element => ( 101 | [ 103 | baseButtonStyle, 104 | { 105 | backgroundColor: buttonFillColor, 106 | opacity: pressed ? 0.8 : 1, 107 | }, 108 | buttonFinishDisabled && { backgroundColor: buttonDisabledColor }, 109 | ]} 110 | onPress={props.onSubmit} 111 | disabled={buttonFinishDisabled} 112 | > 113 | {buttonFinishText} 114 | 115 | ); 116 | 117 | const Container = scrollable ? ScrollView : View; 118 | const containerProps = scrollable ? props.scrollViewProps : props.viewProps; 119 | 120 | return ( 121 | 122 | 123 | {props.children} 124 | 125 | {!removeBtnRow && ( 126 | 135 | {!isPreviousBtnHidden && renderPreviousButton()} 136 | 137 | {activeStep === stepCount - 1 ? renderSubmitButton() : renderNextButton()} 138 | 139 | 140 | )} 141 | 142 | ); 143 | }; 144 | 145 | export default ProgressStep; 146 | -------------------------------------------------------------------------------- /src/ProgressSteps/ProgressSteps.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { View, StyleSheet } from 'react-native'; 3 | import { times } from 'lodash'; 4 | import StepIcon from './StepIcon'; 5 | import type { ProgressStepsProps, ProgressStepsState } from '../types'; 6 | 7 | const ProgressSteps = ({ 8 | children, 9 | isComplete = false, 10 | activeStep: initialActiveStep = 0, 11 | topOffset = 60, 12 | marginBottom = 30, 13 | ...props 14 | }: ProgressStepsProps) => { 15 | const [stepCount, setStepCount] = React.useState(0); 16 | const [activeStep, setActiveStep] = React.useState(initialActiveStep); 17 | 18 | React.useEffect(() => { 19 | setStepCount(React.Children.count(children)); 20 | }, [children]); 21 | 22 | React.useEffect(() => { 23 | setActiveStep(initialActiveStep); 24 | }, [initialActiveStep]); 25 | 26 | const handleSetActiveStep = (step: number): void => { 27 | const boundedStep = Math.min(Math.max(step, 0), stepCount - 1); 28 | setActiveStep(boundedStep); 29 | }; 30 | 31 | const renderStepIcons = (): JSX.Element[] => { 32 | return times(stepCount, (i) => { 33 | const isCompletedStep = isComplete ? true : i < activeStep; 34 | const isActiveStep = isComplete ? false : i === activeStep; 35 | 36 | return ( 37 | 38 | 47 | 48 | ); 49 | }); 50 | }; 51 | 52 | return ( 53 | 54 | {renderStepIcons()} 55 | 56 | {React.cloneElement(children[activeStep], { 57 | setActiveStep: handleSetActiveStep, 58 | activeStep, 59 | stepCount, 60 | })} 61 | 62 | 63 | ); 64 | }; 65 | 66 | const styles = StyleSheet.create({ 67 | container: { 68 | flex: 1, 69 | }, 70 | stepsContainer: { 71 | flexDirection: 'row', 72 | width: '100%', 73 | }, 74 | stepContainer: { 75 | flex: 1, 76 | }, 77 | contentContainer: { 78 | flex: 1, 79 | }, 80 | }); 81 | 82 | export default ProgressSteps; 83 | -------------------------------------------------------------------------------- /src/ProgressSteps/StepIcon.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | import { View, Text, StyleSheet, Dimensions, Animated } from 'react-native'; 3 | import { Check } from 'lucide-react-native'; 4 | import type { StepIconProps } from '../types'; 5 | 6 | const CIRCLE_SIZE = 40; 7 | const MOBILE_BREAKPOINT = 768; 8 | const MOBILE_LINE_POSITION = 78; 9 | const DESKTOP_LINE_POSITION = 58; 10 | 11 | const StepIcon = ({ 12 | borderWidth = 2, 13 | activeStepIconBorderColor = '#2D2D2D', 14 | progressBarColor = '#EBEBE4', 15 | completedProgressBarColor = '#2D2D2D', 16 | activeStepIconColor = 'transparent', 17 | completedStepIconColor = '#2D2D2D', 18 | disabledStepIconColor = '#EBEBE4', 19 | labelColor = '#D3D3D3', 20 | labelFontSize = 14, 21 | activeLabelColor = '#2D2D2D', 22 | completedLabelColor = '#2D2D2D', 23 | activeStepNumColor = '#2D2D2D', 24 | completedStepNumColor = '#2D2D2D', 25 | disabledStepNumColor = '#FFFFFF', 26 | completedCheckColor = '#FFFFFF', 27 | ...props 28 | }: StepIconProps) => { 29 | const { 30 | isActiveStep, 31 | isCompletedStep, 32 | isFirstStep, 33 | isLastStep, 34 | stepNum, 35 | label, 36 | labelFontFamily, 37 | activeLabelFontSize, 38 | } = props; 39 | 40 | const lineAnimationValue = useRef(new Animated.Value(0)).current; 41 | 42 | useEffect(() => { 43 | if (isCompletedStep || isActiveStep) { 44 | Animated.spring(lineAnimationValue, { 45 | toValue: 1, 46 | useNativeDriver: false, 47 | tension: 25, 48 | friction: 25, 49 | }).start(); 50 | } else { 51 | lineAnimationValue.setValue(0); 52 | } 53 | }, [isCompletedStep, isActiveStep]); 54 | 55 | const getLinePosition = () => { 56 | const screenWidth = Dimensions.get('window').width; 57 | const isMobileWidth = screenWidth <= MOBILE_BREAKPOINT; 58 | return isMobileWidth ? MOBILE_LINE_POSITION : DESKTOP_LINE_POSITION; 59 | }; 60 | 61 | const getLineColor = (isLeftLine: boolean) => { 62 | if (isLeftLine && (isCompletedStep || isActiveStep)) { 63 | return completedProgressBarColor; 64 | } 65 | 66 | if (!isLeftLine && isCompletedStep) { 67 | return completedProgressBarColor; 68 | } 69 | 70 | return progressBarColor; 71 | }; 72 | 73 | const getStepColor = () => { 74 | if (isActiveStep) return activeStepIconColor; 75 | if (isCompletedStep) return completedStepIconColor; 76 | return disabledStepIconColor; 77 | }; 78 | 79 | const getLabelColor = () => { 80 | if (isActiveStep) return activeLabelColor; 81 | if (isCompletedStep) return completedLabelColor; 82 | return labelColor; 83 | }; 84 | 85 | const getNumberColor = () => { 86 | if (isActiveStep) return activeStepNumColor; 87 | if (isCompletedStep) return completedStepNumColor; 88 | return disabledStepNumColor; 89 | }; 90 | 91 | const linePosition = getLinePosition(); 92 | 93 | const renderLine = (isLeft: boolean) => ( 94 | 105 | 118 | 119 | ); 120 | 121 | return ( 122 | 123 | 124 | {!isFirstStep && renderLine(true)} 125 | 140 | {isCompletedStep ? ( 141 | 142 | ) : ( 143 | {stepNum} 144 | )} 145 | 146 | {!isLastStep && renderLine(false)} 147 | 148 | 159 | {label} 160 | 161 | 162 | ); 163 | }; 164 | 165 | const styles = StyleSheet.create({ 166 | container: { 167 | alignItems: 'center', 168 | }, 169 | lineContainer: { 170 | width: '100%', 171 | flexDirection: 'row', 172 | alignItems: 'center', 173 | justifyContent: 'center', 174 | position: 'relative', 175 | }, 176 | circle: { 177 | justifyContent: 'center', 178 | alignItems: 'center', 179 | zIndex: 2, 180 | }, 181 | line: { 182 | zIndex: 1, 183 | }, 184 | stepText: { 185 | fontSize: 16, 186 | fontWeight: '500', 187 | }, 188 | label: { 189 | textAlign: 'center', 190 | marginTop: 8, 191 | maxWidth: '80%', 192 | }, 193 | }); 194 | 195 | export default StepIcon; 196 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ProgressSteps } from './ProgressSteps/ProgressSteps'; 2 | export { default as ProgressStep } from './ProgressSteps/ProgressStep'; 3 | 4 | export type { ProgressStepsProps, ProgressStepProps } from './types'; 5 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import type { ScrollViewProps, ViewProps } from 'react-native'; 2 | export interface ProgressStepsProps 3 | extends Omit { 4 | isComplete?: boolean; 5 | activeStep?: number; 6 | topOffset?: number; 7 | marginBottom?: number; 8 | children: React.ReactElement[]; 9 | } 10 | 11 | export interface ProgressStepsState { 12 | stepCount: number; 13 | activeStep: number; 14 | } 15 | 16 | export interface ProgressStepProps { 17 | // Internal props (set by parent) 18 | setActiveStep?: (step: number) => void; 19 | activeStep?: number; 20 | stepCount?: number; 21 | 22 | // User-provided props 23 | label?: string; 24 | onNext?: () => void; 25 | onPrevious?: () => void; 26 | onSubmit?: () => void; 27 | scrollViewProps?: ScrollViewProps; 28 | scrollable?: boolean; 29 | viewProps?: ViewProps; 30 | errors?: boolean; 31 | removeBtnRow?: boolean; 32 | children?: React.ReactNode; 33 | buttonNextText?: string; 34 | buttonPreviousText?: string; 35 | buttonFinishText?: string; 36 | buttonNextDisabled?: boolean; 37 | buttonPreviousDisabled?: boolean; 38 | buttonFinishDisabled?: boolean; 39 | buttonTopOffset?: number; 40 | buttonBottomOffset?: number; 41 | buttonHorizontalOffset?: number; 42 | buttonFillColor?: string; 43 | buttonBorderColor?: string; 44 | buttonNextTextColor?: string; 45 | buttonPreviousTextColor?: string; 46 | buttonFinishTextColor?: string; 47 | buttonDisabledColor?: string; 48 | buttonDisabledTextColor?: string; 49 | } 50 | 51 | export interface StepIconProps { 52 | // Required props 53 | stepNum: number; 54 | isFirstStep: boolean; 55 | isLastStep: boolean; 56 | isActiveStep: boolean; 57 | isCompletedStep: boolean; 58 | label?: string; 59 | 60 | // Style props (all optional with defaults) 61 | borderWidth?: number; 62 | activeStepIconBorderColor?: string; 63 | progressBarColor?: string; 64 | completedProgressBarColor?: string; 65 | activeStepIconColor?: string; 66 | disabledStepIconColor?: string; 67 | completedStepIconColor?: string; 68 | labelFontFamily?: string; 69 | labelColor?: string; 70 | labelFontSize?: number; 71 | activeLabelColor?: string; 72 | activeLabelFontSize?: number; 73 | completedLabelColor?: string; 74 | activeStepNumColor?: string; 75 | completedStepNumColor?: string; 76 | disabledStepNumColor?: string; 77 | completedCheckColor?: string; 78 | } 79 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "allowJs": true, 7 | "strict": true, 8 | "jsx": "react", 9 | "esModuleInterop": true, 10 | "resolveJsonModule": true, 11 | "skipLibCheck": true, 12 | "lib": ["esnext", "dom"], 13 | "baseUrl": ".", 14 | "allowUnreachableCode": false, 15 | "allowUnusedLabels": false, 16 | "forceConsistentCasingInFileNames": true, 17 | "noImplicitUseStrict": false, 18 | "noStrictGenericChecks": false, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "declaration": true, 22 | "declarationMap": true, 23 | "outDir": "dist", 24 | "rootDir": "src", 25 | "allowSyntheticDefaultImports": true 26 | }, 27 | "include": ["src"], 28 | "exclude": ["node_modules", "dist", "examples", "**/__tests__/**"] 29 | } 30 | --------------------------------------------------------------------------------