├── .eslintrc.json ├── README.md ├── index.js ├── last.png ├── lib ├── Button.js ├── MultiSwitch.js ├── assets │ └── slider │ │ ├── active │ │ ├── complete@2x.png │ │ ├── complete@3x.png │ │ ├── inprogress@2x.png │ │ ├── inprogress@3x.png │ │ ├── notstarted@2x.png │ │ └── notstarted@3x.png │ │ └── inactive │ │ ├── complete@2x.png │ │ ├── complete@3x.png │ │ ├── inprogress@2x.png │ │ ├── inprogress@3x.png │ │ ├── notstarted@2x.png │ │ └── notstarted@3x.png └── styles.js ├── one.png └── package.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "browser": true, 5 | "node": true, 6 | "jest": true 7 | }, 8 | "parser": "babel-eslint", 9 | "extends": [ 10 | "eslint:recommended", 11 | "plugin:react/recommended", 12 | "plugin:react-native/all" 13 | ], 14 | "parserOptions": { 15 | "ecmaFeatures": { 16 | "jsx": true 17 | }, 18 | "sourceType": "module" 19 | }, 20 | "plugins": [ 21 | "react", 22 | "react-native" 23 | ], 24 | "rules": { 25 | "indent": [ 26 | "error", 27 | 4 28 | ], 29 | "linebreak-style": [ 30 | "error", 31 | "unix" 32 | ], 33 | "quotes": [ 34 | "error", 35 | "single" 36 | ], 37 | "semi": [ 38 | "error", 39 | "always" 40 | ], 41 | "no-var": [ 42 | "error" 43 | ], 44 | "eqeqeq": [ 45 | "error" 46 | ] 47 | }, 48 | "globals": { 49 | "fetch": true 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rn-slider-switch 2 | Multi slider switch component in React Native (iOs & android) 3 | 4 | 5 | 6 | ## Installation: 7 | 8 | Install the component through npm using: 9 | 10 | ``` 11 | npm install rn-slider-switch --save 12 | ``` 13 | 14 | ![alt text](https://raw.githubusercontent.com/victorkvarghese/rn-slider-switch/master/one.png) 15 | ![alt text](https://raw.githubusercontent.com/victorkvarghese/rn-slider-switch/master/last.png) 16 | 17 | [YOUTUBE : See slider in action](https://www.youtube.com/watch?v=d7oeRdoRyFk&feature=youtu.be) 18 | 19 | ## Properties 20 | 21 | | Prop | Description | 22 | | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | 23 | | **`currentStatus`** | Initial Status of the slider . Defaults to 'Open'. Other values include 'In Progress', 'Complete' | 24 | | **`onStatusChanged`** | Called when status changes in Slider | 25 | | **`isParentScrollDisabled`** | Whether scroll is disabled in Parent.(Optional) | 26 | | **`disableScroll`** | Used to disable scroll in parent .. Works as callback function if u want to disable scroll in parent.(Optional) | 27 | | **`disableSwitch`** | Used to disable switch (Click & scroll will not work) .. (Optional) | 28 | 29 | 30 | 31 | 32 | ## Example: 33 | ``` 34 | import MultiSwitch from 'rn-slider-switch'; 35 | { 38 | console.log('scrollEnabled', value); 39 | // this.scrollView.setNativeProps({ 40 | // scrollEnabled: value 41 | // }); 42 | }} 43 | isParentScrollEnabled={false} 44 | onStatusChanged={text => { 45 | console.log('Change Status ', text); 46 | }}/> 47 | ``` 48 | 49 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import MultiSwitch from "./lib/MultiSwitch"; 2 | export default MultiSwitch; 3 | -------------------------------------------------------------------------------- /last.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/victorkvarghese/rn-slider-switch/13d29e253822189cb995c85e4c26ae821efb6088/last.png -------------------------------------------------------------------------------- /lib/Button.js: -------------------------------------------------------------------------------- 1 | /* Switch Button Component class 2 | */ 3 | import React from 'react'; 4 | import { Image, TouchableOpacity, View } from 'react-native'; 5 | import styles from './styles'; 6 | import PropTypes from 'prop-types'; 7 | 8 | const getIcon = (type, active) => { 9 | let icn; 10 | switch (type) { 11 | case 'Open': 12 | icn = active 13 | ? require('./assets/slider/active/notstarted.png') 14 | : require('./assets/slider/inactive/notstarted.png'); 15 | break; 16 | case 'In Progress': 17 | icn = active 18 | ? require('./assets/slider/active/inprogress.png') 19 | : require('./assets/slider/inactive/inprogress.png'); 20 | break; 21 | case 'Complete': 22 | icn = active 23 | ? require('./assets/slider/active/complete.png') 24 | : require('./assets/slider/inactive/complete.png'); 25 | break; 26 | } 27 | return icn; 28 | }; 29 | 30 | const Button = props => { 31 | return ( 32 | 33 | 37 | 38 | 39 | 40 | ); 41 | }; 42 | 43 | Button.propTypes = { 44 | type: PropTypes.string, 45 | active: PropTypes.bool, 46 | onPress: PropTypes.func 47 | }; 48 | 49 | Button.defaultProps = { 50 | active: false 51 | }; 52 | 53 | export default Button; 54 | -------------------------------------------------------------------------------- /lib/MultiSwitch.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { 3 | Animated, 4 | Dimensions, 5 | PanResponder, 6 | View, 7 | Platform 8 | } from "react-native"; 9 | import Button from "./Button"; 10 | import styles from "./styles"; 11 | const { width } = Dimensions.get("window"); 12 | import PropTypes from "prop-types"; 13 | 14 | export default class MultiSwitch extends Component { 15 | constructor(props) { 16 | super(props); 17 | this.state = { 18 | currentStatus: props.currentStatus, 19 | isComponentReady: false, 20 | position: new Animated.Value(0), 21 | posValue: 0, 22 | selectedPosition: 0, 23 | duration: 100, 24 | mainWidth: width - 30, 25 | switcherWidth: width / 2.7, 26 | thresholdDistance: width - 8 - width / 2.4 27 | }; 28 | this.isParentScrollDisabled = false; 29 | } 30 | 31 | componentWillMount() { 32 | this._panResponder = PanResponder.create({ 33 | onStartShouldSetPanResponder: () => true, 34 | onStartShouldSetPanResponderCapture: () => true, 35 | onMoveShouldSetPanResponder: () => true, 36 | onMoveShouldSetPanResponderCapture: () => true, 37 | 38 | onPanResponderGrant: () => { 39 | // disable parent scroll if slider is inside a scrollview 40 | if (!this.isParentScrollDisabled) { 41 | this.props.disableScroll(false); 42 | this.isParentScrollDisabled = true; 43 | } 44 | }, 45 | 46 | onPanResponderMove: (evt, gestureState) => { 47 | if (!this.props.disableSwitch) { 48 | let finalValue = gestureState.dx + this.state.posValue; 49 | if (finalValue >= 0 && finalValue <= this.state.thresholdDistance) 50 | this.state.position.setValue(this.state.posValue + gestureState.dx); 51 | } 52 | }, 53 | 54 | onPanResponderTerminationRequest: () => true, 55 | 56 | onPanResponderRelease: (evt, gestureState) => { 57 | if (!this.props.disableSwitch) { 58 | let finalValue = gestureState.dx + this.state.posValue; 59 | this.isParentScrollDisabled = false; 60 | this.props.disableScroll(true); 61 | if (gestureState.dx > 0) { 62 | if (finalValue >= 0 && finalValue <= 30) { 63 | this.notStartedSelected(); 64 | } else if (finalValue >= 30 && finalValue <= 121) { 65 | this.inProgressSelected(); 66 | } else if (finalValue >= 121 && finalValue <= 280) { 67 | if (gestureState.dx > 0) { 68 | this.completeSelected(); 69 | } else { 70 | this.inProgressSelected(); 71 | } 72 | } 73 | } else { 74 | if (finalValue >= 78 && finalValue <= 175) { 75 | this.inProgressSelected(); 76 | } else if (finalValue >= -100 && finalValue <= 78) { 77 | this.notStartedSelected(); 78 | } else { 79 | this.completeSelected(); 80 | } 81 | } 82 | } 83 | }, 84 | 85 | onPanResponderTerminate: () => {}, 86 | onShouldBlockNativeResponder: () => { 87 | // Returns whether this component should block native components from becoming the JS 88 | // responder. Returns true by default. Is currently only supported on android. 89 | return true; 90 | } 91 | }); 92 | this.moveInitialState(); 93 | } 94 | 95 | notStartedSelected = () => { 96 | if (this.props.disableSwitch) return; 97 | Animated.timing(this.state.position, { 98 | toValue: Platform.OS === "ios" ? -2 : 0, 99 | duration: this.state.duration 100 | }).start(); 101 | setTimeout(() => { 102 | this.setState({ 103 | posValue: Platform.OS === "ios" ? -2 : 0, 104 | selectedPosition: 0 105 | }); 106 | }, 100); 107 | this.props.onStatusChanged("Open"); 108 | }; 109 | 110 | inProgressSelected = () => { 111 | if (this.props.disableSwitch) return; 112 | Animated.timing(this.state.position, { 113 | toValue: this.state.mainWidth / 2 - this.state.switcherWidth / 2, 114 | duration: this.state.duration 115 | }).start(); 116 | setTimeout(() => { 117 | this.setState({ 118 | posValue: this.state.mainWidth / 2 - this.state.switcherWidth / 2, 119 | selectedPosition: 1 120 | }); 121 | }, 100); 122 | this.props.onStatusChanged("In Progress"); 123 | }; 124 | 125 | completeSelected = () => { 126 | if (this.props.disableSwitch) return; 127 | Animated.timing(this.state.position, { 128 | toValue: 129 | Platform.OS === "ios" 130 | ? this.state.mainWidth - this.state.switcherWidth 131 | : this.state.mainWidth - this.state.switcherWidth - 2, 132 | duration: this.state.duration 133 | }).start(); 134 | setTimeout(() => { 135 | this.setState({ 136 | posValue: 137 | Platform.OS === "ios" 138 | ? this.state.mainWidth - this.state.switcherWidth 139 | : this.state.mainWidth - this.state.switcherWidth - 2, 140 | selectedPosition: 2 141 | }); 142 | }, 100); 143 | this.props.onStatusChanged("Complete"); 144 | }; 145 | 146 | getStatus = () => { 147 | switch (this.state.selectedPosition) { 148 | case 0: 149 | return "Open"; 150 | case 1: 151 | return "In Progress"; 152 | case 2: 153 | return "Complete"; 154 | } 155 | }; 156 | 157 | moveInitialState = () => { 158 | switch (this.state.currentStatus) { 159 | case "Open": 160 | this.notStartedSelected(); 161 | break; 162 | case "In Progress": 163 | this.inProgressSelected(); 164 | break; 165 | case "Complete": 166 | this.completeSelected(); 167 | break; 168 | } 169 | }; 170 | 171 | render() { 172 | return ( 173 | 174 |