├── .gitignore ├── README.md ├── header_github-open.png ├── package.json ├── resources └── img │ ├── bellIcon.png │ ├── leverIcon.png │ ├── messageIcon.png │ ├── socialIcon.png │ └── tuneIcon.png └── src ├── BarPanel ├── barPanel.js ├── index.js └── styles.js ├── TabButton ├── TabBarAnimations.js ├── index.js ├── styles.js └── tabButton.js ├── TabIcons ├── TuneViewIcon │ ├── index.js │ └── styles.js └── index.js ├── TabNavigation ├── index.js ├── navigatorView.js └── styles.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | package-lock.json 5 | yarn.lock 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 | iOS iOS 8 | 9 | npm compatible 10 | Twitter 11 | Facebook 12 | Medium 13 | 14 |

15 | Medium 16 | 17 | # About 18 | Our company provides custom UI design and development solutions for mobile applications and websites. 19 | 20 | Need a team to create a project? 21 | 22 | This project is developed and maintained by openGeeksLab LLC. 23 | 24 | 25 | 26 | 27 | # react-native-tab-navigator 28 | 29 | ## Requirements 30 | - React Native 0.50+ 31 | - iOS 9.0+ 32 | - Android 4.2+ 33 | 34 | ## Installation 35 | Just run: 36 | - npm i @opengeekslab/react-native-tab-navigator 37 | 38 | ## Basic usage 39 | ```javascript 40 | import React, { Component } from 'react'; 41 | import { StyleSheet, View } from 'react-native'; 42 | import { TabNavigation, TabButton, TabIcons } from '@opengeekslab/react-native-tab-navigator'; 43 | 44 | import Screen1 from './screens/screen1'; 45 | import Screen2 from './screens/screen2'; 46 | import Screen3 from './screens/screen3'; 47 | import Screen4 from './screens/screen4'; 48 | import Screen5 from './screens/screen5'; 49 | 50 | const navigationRouter = { 51 | Screen_1: { 52 | screen: Screen1, 53 | screenOptions: { 54 | title: 'Item 1', 55 | showTitle: true, 56 | animated: true, 57 | tabIcon: TabIcons.Social, 58 | animation: [ 59 | { 60 | name: 'rotationY', 61 | type: 'bouncing', 62 | }, 63 | ], 64 | }, 65 | }, 66 | Screen_2: { 67 | screen: Screen2, 68 | screenOptions: { 69 | title: 'Item 2', 70 | showTitle: true, 71 | tabIcon: TabIcons.Message, 72 | animation: [ 73 | 'ripple', 74 | { name: 'scale' }, 75 | { 76 | name: 'fume', 77 | duration: 700, 78 | }, 79 | { 80 | name: 'fadeOut', 81 | duration: 700, 82 | }, 83 | ], 84 | }, 85 | }, 86 | Screen_3: { 87 | screen: Screen3, 88 | screenOptions: { 89 | title: 'Item 3', 90 | tabIcon: TabIcons.TuneView, 91 | animated: true, 92 | animation: [], 93 | }, 94 | }, 95 | Screen_4: { 96 | screen: Screen4, 97 | screenOptions: { 98 | title: 'Item 4', 99 | tabIcon: TabIcons.Bell, 100 | animation: [ 101 | { 102 | name: 'pendulum', 103 | duration: 700, 104 | }, 105 | ], 106 | }, 107 | }, 108 | Screen_5: { 109 | screen: Screen5, 110 | screenOptions: { 111 | title: 'Item 5', 112 | tabIcon: TabIcons.Lever, 113 | animation: [ 114 | { 115 | name: 'scale', 116 | type: 'bouncing', 117 | }, 118 | ], 119 | }, 120 | }, 121 | }; 122 | 123 | const defaultConfig = { 124 | lazy: true, 125 | defaultRoute: 'Screen_3', 126 | screenOptions: { 127 | showTitle: true, 128 | animated: true, 129 | buttonView: TabButton, 130 | activeTintColor: '#0579fc', 131 | inactiveTintColor: '#818692', 132 | animation: ['ripple', 'rotationZ'], 133 | }, 134 | }; 135 | 136 | const TabNavigation = TabNavigation(navigationRouter, defaultConfig); 137 | 138 | type Props = {}; 139 | export default class App extends Component { 140 | render() { 141 | return ( 142 | 143 | 144 | 145 | ); 146 | } 147 | } 148 | 149 | const styles = StyleSheet.create({ 150 | container: { 151 | flex: 1, 152 | backgroundColor: '#F5FCFF', 153 | }, 154 | }); 155 | ``` 156 | 157 | To add a screen to the tab bar, you need to create an object containing information about routers. 158 | ```javascript 159 | Screen_1: { 160 | screen: Screen1, 161 | screenOptions: { 162 | title: 'Item 1', 163 | showTitle: true, 164 | animated: true, 165 | tabIcon: TabIcons.Social, 166 | animation: [ 167 | { 168 | name: 'rotationY', 169 | type: 'bouncing', 170 | }, 171 | ], 172 | }, 173 | } 174 | ``` 175 | 244 | 245 | ## Animations usage 246 | The library provides animations fume, pendulum, rotationX, rotationY, rotationZ, opacity, scale, ripple. All animations can be combined. 247 | ```javascript 248 | animation: [ 249 | 'ripple', 250 | 'scale', 251 | ] 252 | ``` 253 | Also, animations can be additionally set aside. If you transfer an object to the animation array that consists of the field 'name' - the name of the animation and the type: "bouncing", the animation will have an "elastic effect" (except Ripple). 254 | ```javascript 255 | animation: [{ 256 | name: 'scale', 257 | type: 'bouncing', 258 | }] 259 | ``` 260 | 261 | In the field of durations, you specify the animation time in milliseconds (400ms by default) 262 | ```javascript 263 | animation: [ 264 | { 265 | name: 'fume', 266 | duration: 700, 267 | }, 268 | { 269 | name: 'fadeOut', 270 | duration: 700, 271 | }, 272 | ] 273 | ``` 274 | 275 | The library provides built-in icons for ```Social```, ```Message```, ```TuneView```, ```Bell```, ```Lever```, ```Tune```. 276 | They can be imported 277 | ```javascript 278 | import { TabIcons } from 'react-native-tab-navigator'; 279 | { 280 | title: 'Item 5', 281 | tabIcon: TabIcons.Lever 282 | } 283 | ``` 284 | You can transfer your icon 285 | ```javascript 286 | const myIcon = require('./my-icon-file.png'); 287 | { 288 | title: 'Item 5', 289 | tabIcon: myIcon 290 | } 291 | ``` 292 | You can send a component as an icon. 293 | If the component implements internal animations, it must provide a ```callAnimations()``` method to call the animations. 294 | # Contact us if interested. 295 | 296 | 297 | 298 | # Inspired by @Ramotion 299 | # Licence 300 | Expanding is released under the MIT license. 301 | 302 | 303 | -------------------------------------------------------------------------------- /header_github-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openGeeksLab/react-native-tab-navigator/9f4f3166fb40b223373ccd5767aacdd71a5527fa/header_github-open.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@opengeekslab/react-native-tab-navigator", 3 | "version": "1.0.3", 4 | "description": "A tab bar that switches between scenes, written in JS for cross-platform support", 5 | "main": "src/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/openGeeksLab/react-native-tab-navigator" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [ 14 | "react-native", 15 | "tab-bar", 16 | "navigator", 17 | "ios", 18 | "android" 19 | ], 20 | "author": "openGeeksLab", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/openGeeksLab/react-native-tab-navigator/issues" 24 | }, 25 | "homepage": "https://github.com/openGeeksLab/react-native-tab-navigator/#README.md", 26 | "dependencies": { 27 | "prop-types": "^15.6.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /resources/img/bellIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openGeeksLab/react-native-tab-navigator/9f4f3166fb40b223373ccd5767aacdd71a5527fa/resources/img/bellIcon.png -------------------------------------------------------------------------------- /resources/img/leverIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openGeeksLab/react-native-tab-navigator/9f4f3166fb40b223373ccd5767aacdd71a5527fa/resources/img/leverIcon.png -------------------------------------------------------------------------------- /resources/img/messageIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openGeeksLab/react-native-tab-navigator/9f4f3166fb40b223373ccd5767aacdd71a5527fa/resources/img/messageIcon.png -------------------------------------------------------------------------------- /resources/img/socialIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openGeeksLab/react-native-tab-navigator/9f4f3166fb40b223373ccd5767aacdd71a5527fa/resources/img/socialIcon.png -------------------------------------------------------------------------------- /resources/img/tuneIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openGeeksLab/react-native-tab-navigator/9f4f3166fb40b223373ccd5767aacdd71a5527fa/resources/img/tuneIcon.png -------------------------------------------------------------------------------- /src/BarPanel/barPanel.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { View, Platform, Dimensions } from 'react-native'; 4 | 5 | import styles from './styles'; 6 | 7 | const { width } = Dimensions.get('screen'); 8 | const buttonAnimatedViewWidth = buttonsArrayLength => width / buttonsArrayLength; 9 | 10 | class BarPanel extends Component { 11 | static propTypes = { 12 | renderButton: PropTypes.func.isRequired, 13 | buttonsConfiguration: PropTypes.array.isRequired, 14 | } 15 | 16 | componentWillMount() { 17 | Dimensions.addEventListener('change', () => { 18 | this.forceUpdate(); 19 | }); 20 | } 21 | 22 | renderButtons = (buttonsConfiguration) => { 23 | const viewWidth = buttonAnimatedViewWidth(buttonsConfiguration.length); 24 | let isAnimated = false; 25 | const buttonsArray = buttonsConfiguration.map((item, index) => { 26 | const { renderButton } = this.props; 27 | if (Platform.OS === 'android' && item.animated) { 28 | isAnimated = true; 29 | return ( 30 | 40 | 41 | {renderButton(item, viewWidth)} 42 | 43 | ); 44 | } 45 | return ( 46 | 50 | {renderButton(item)} 51 | 52 | ); 53 | }); 54 | 55 | if (isAnimated) { 56 | return (buttonsArray); 57 | } 58 | 59 | return ( 60 | 61 | {buttonsArray} 62 | 63 | ); 64 | } 65 | 66 | render() { 67 | const { buttonsConfiguration } = this.props; 68 | return this.renderButtons(buttonsConfiguration); 69 | } 70 | } 71 | 72 | export default BarPanel; 73 | -------------------------------------------------------------------------------- /src/BarPanel/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import BarPanel from './barPanel'; 5 | 6 | class BarPanelContainer extends Component { 7 | static propTypes = { 8 | navigate: PropTypes.func.isRequired, 9 | buttonsConfiguration: PropTypes.array.isRequired, 10 | } 11 | 12 | onButtonPress = (buttonConfig) => { 13 | const { navigate } = this.props; 14 | navigate(buttonConfig.key); 15 | } 16 | 17 | renderButton = (buttonConfig, viewWidth) => { 18 | const TabButton = buttonConfig.buttonView; 19 | 20 | return ( 21 | this.onButtonPress(buttonConfig)} 24 | buttonConfiguration={buttonConfig} 25 | /> 26 | ); 27 | } 28 | 29 | render() { 30 | const { buttonsConfiguration } = this.props; 31 | 32 | return ( 33 | 37 | ); 38 | } 39 | } 40 | 41 | export default BarPanelContainer; 42 | -------------------------------------------------------------------------------- /src/BarPanel/styles.js: -------------------------------------------------------------------------------- 1 | import { 2 | StyleSheet, 3 | Dimensions, 4 | Platform, 5 | } from 'react-native'; 6 | 7 | const { height, width } = Dimensions.get('screen'); 8 | 9 | export const isIOSX = Platform.OS === 'ios' 10 | && (height === 812 || width === 812); 11 | 12 | const styles = StyleSheet.create({ 13 | container: { 14 | width: '100%', 15 | ...Platform.select({ 16 | ios: { 17 | height: isIOSX ? 75 : 60, 18 | }, 19 | android: { 20 | height: 60, 21 | }, 22 | }), 23 | backgroundColor: '#faf8fa', 24 | borderTopColor: '#edecef', 25 | borderTopWidth: StyleSheet.hairlineWidth, 26 | flexDirection: 'row', 27 | justifyContent: 'space-around', 28 | paddingTop: 5, 29 | position: 'absolute', 30 | bottom: 0, 31 | left: 0, 32 | }, 33 | buttonContainer: { 34 | justifyContent: 'center', 35 | alignItems: 'center', 36 | }, 37 | animatedButtonContainer: { 38 | position: 'absolute', 39 | bottom: 0, 40 | height: 100, 41 | justifyContent: 'flex-end', 42 | alignItems: 'center', 43 | }, 44 | fakePanelcontainer: { 45 | position: 'absolute', 46 | left: 0, 47 | bottom: 0, 48 | width: '100%', 49 | height: 60, 50 | backgroundColor: '#faf8fa', 51 | borderTopColor: '#edecef', 52 | borderTopWidth: StyleSheet.hairlineWidth, 53 | }, 54 | }); 55 | 56 | export default styles; 57 | -------------------------------------------------------------------------------- /src/TabButton/TabBarAnimations.js: -------------------------------------------------------------------------------- 1 | import { Animated } from 'react-native'; 2 | 3 | const fadeOutInterpolation = { 4 | inputRange: [0, 1], 5 | outputRange: [1, 0], 6 | }; 7 | 8 | const scaleInterpolatation = { 9 | inputRange: [0, 0.7, 1], 10 | outputRange: [1, 1.5, 1], 11 | }; 12 | 13 | const rotationInterpolation = { 14 | inputRange: [0, 1], 15 | outputRange: ['0deg', '360deg'], 16 | }; 17 | 18 | const translationInterpolation = { 19 | inputRange: [0, 0.3, 1], 20 | outputRange: [0, 15, -90], 21 | }; 22 | 23 | const pendulumInterpolation = { 24 | inputRange: [0, 0.20, 0.40, 0.60, 0.80, 1], 25 | outputRange: ['0deg', '60deg', '-60deg', '30deg', '-30deg', '0deg'], 26 | }; 27 | 28 | const SPRING_CONFIG = { tension: 2, friction: 2 }; 29 | 30 | class TabBarAnimations { 31 | constructor(animationsArray) { 32 | this.animatedStyle = {}; 33 | this.animations = { 34 | animatedValue: new Animated.Value(0), 35 | }; 36 | 37 | this.animations = animationsArray.map((animationItem) => { 38 | const isObject = animationItem !== null && typeof animationItem === 'object'; 39 | let animationType = 'timing'; 40 | let animationDuration = 400; 41 | 42 | if (isObject) { 43 | animationType = animationItem.type === 'bouncing' ? animationItem.type : 'timing'; 44 | animationDuration = !Number.isNaN(animationItem.duration) ? animationItem.duration : 400; 45 | } 46 | const animationName = isObject ? animationItem.name : animationItem; 47 | 48 | const animationInformaion = { 49 | type: animationType, 50 | duration: animationDuration, 51 | animation: new Animated.Value(0), 52 | }; 53 | 54 | switch (animationName) { 55 | case 'scale': 56 | animationInformaion.interpolation = { 57 | scale: animationInformaion.animation.interpolate(scaleInterpolatation), 58 | }; 59 | break; 60 | case 'rotationX': 61 | animationInformaion.interpolation = { 62 | rotateX: animationInformaion.animation.interpolate(rotationInterpolation), 63 | }; 64 | break; 65 | case 'pendulum': 66 | animationInformaion.interpolation = { 67 | rotateZ: animationInformaion.animation.interpolate(pendulumInterpolation), 68 | }; 69 | break; 70 | case 'rotationY': 71 | animationInformaion.interpolation = { 72 | rotateY: animationInformaion.animation.interpolate(rotationInterpolation), 73 | }; 74 | break; 75 | case 'rotationZ': 76 | animationInformaion.interpolation = { 77 | rotateZ: animationInformaion.animation.interpolate(rotationInterpolation), 78 | }; 79 | break; 80 | case 'fume': 81 | animationInformaion.interpolation = { 82 | translateY: animationInformaion.animation.interpolate(translationInterpolation), 83 | }; 84 | break; 85 | case 'fadeOut': 86 | animationInformaion.style = { 87 | opacity: animationInformaion.animation.interpolate(fadeOutInterpolation), 88 | }; 89 | break; 90 | default: 91 | animationInformaion.interpolation = { translateX: 0 }; 92 | break; 93 | } 94 | return animationInformaion; 95 | }); 96 | } 97 | 98 | getAnimatedStyle() { 99 | let resultStyle = {}; 100 | const interpolation = []; 101 | for (let i = 0; i < this.animations.length; i++) { 102 | const animation = this.animations[i]; 103 | if (animation.interpolation) { 104 | interpolation.push(animation.interpolation); 105 | } else { 106 | resultStyle = { 107 | ...resultStyle, 108 | ...animation.style, 109 | }; 110 | } 111 | resultStyle.transform = interpolation; 112 | } 113 | return resultStyle; 114 | } 115 | 116 | getAnimations() { 117 | return this.animations.map((item) => { 118 | if (item.type === 'bouncing') { 119 | return Animated.spring( 120 | item.animation, 121 | { 122 | toValue: 1, 123 | ...SPRING_CONFIG, 124 | }, 125 | ); 126 | } 127 | return Animated.timing( 128 | item.animation, 129 | { 130 | toValue: 1, 131 | duration: item.duration, 132 | }, 133 | ); 134 | }); 135 | } 136 | 137 | resetAnimations() { 138 | this.animations.forEach((item) => { 139 | item.animation.setValue(0); 140 | }); 141 | } 142 | 143 | callAnimations() { 144 | Animated.parallel([ 145 | ...this.getAnimations(), 146 | ]).start(() => { 147 | this.resetAnimations(); 148 | }); 149 | } 150 | } 151 | 152 | export default TabBarAnimations; 153 | -------------------------------------------------------------------------------- /src/TabButton/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import TabButton from './tabButton'; 5 | 6 | class TabButtonContainer extends Component { 7 | static propTypes = { 8 | onPress: PropTypes.func.isRequired, 9 | viewWidth: PropTypes.oneOfType([PropTypes.number, undefined]), 10 | buttonConfiguration: PropTypes.object.isRequired, 11 | } 12 | 13 | render() { 14 | const { 15 | onPress, 16 | viewWidth, 17 | buttonConfiguration, 18 | } = this.props; 19 | 20 | return ( 21 | 26 | ); 27 | } 28 | } 29 | 30 | export default TabButtonContainer; 31 | -------------------------------------------------------------------------------- /src/TabButton/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Platform } from 'react-native'; 2 | 3 | const styles = StyleSheet.create({ 4 | container: { 5 | flex: 1, 6 | alignItems: 'center', 7 | justifyContent: 'center', 8 | }, 9 | touchableView: { 10 | flex: 0, 11 | padding: 3, 12 | alignItems: 'center', 13 | justifyContent: 'center', 14 | }, 15 | iconImageContianer: { 16 | width: 30, 17 | height: 25, 18 | justifyContent: 'center', 19 | alignItems: 'center', 20 | marginBottom: 3, 21 | }, 22 | iconImage: { 23 | width: 25, 24 | height: 21, 25 | }, 26 | titleContainer: { 27 | height: 22, 28 | }, 29 | titleText: { 30 | fontSize: 10, 31 | lineHeight: 12, 32 | color: '#818692', 33 | textAlign: 'center', 34 | backgroundColor: 'transparent', 35 | }, 36 | rippleViewContainer: { 37 | ...Platform.select({ 38 | ios: { 39 | height: '100%', 40 | }, 41 | android: { 42 | height: 150, 43 | paddingTop: 48, 44 | }, 45 | }), 46 | position: 'absolute', 47 | top: 0, 48 | left: 0, 49 | width: '100%', 50 | backgroundColor: 'transparent', 51 | alignItems: 'center', 52 | justifyContent: 'flex-start', 53 | }, 54 | rippleViewAnimated: { 55 | width: 30, 56 | height: 30, 57 | borderRadius: 15, 58 | backgroundColor: 'transparent', 59 | alignItems: 'center', 60 | }, 61 | buttonIOSContainer: { 62 | alignItems: 'flex-start', 63 | }, 64 | buttonAndroidContainer: { 65 | alignItems: 'flex-start', 66 | marginTop: 5, 67 | width: '100%', 68 | height: '100%', 69 | justifyContent: 'flex-end', 70 | paddingTop: 60, 71 | }, 72 | touchableAnimatedView: { 73 | top: 50, 74 | height: 50, 75 | position: 'absolute', 76 | justifyContent: 'flex-end', 77 | }, 78 | }); 79 | 80 | export default styles; 81 | -------------------------------------------------------------------------------- /src/TabButton/tabButton.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { 4 | TouchableOpacity, 5 | Animated, 6 | Platform, 7 | Easing, 8 | Image, 9 | View, 10 | Text, 11 | } from 'react-native'; 12 | 13 | import styles from './styles'; 14 | 15 | import TabAnimations from './TabBarAnimations'; 16 | 17 | const animationDuration = 400; 18 | 19 | const borderInterpolationConfig = { 20 | inputRange: [0, 1], 21 | outputRange: [20, 0], 22 | }; 23 | 24 | const rippleOpacityInterpolationConfig = { 25 | inputRange: [0, 0.1, 1], 26 | outputRange: [0, 0.3, 0.7], 27 | }; 28 | 29 | const viewScaleInterpolationConfig = { 30 | inputRange: [0, 1], 31 | outputRange: [0.1, 2], 32 | }; 33 | 34 | class TabButton extends Component { 35 | static propTypes = { 36 | onPress: PropTypes.func.isRequired, 37 | viewWidth: PropTypes.oneOfType([PropTypes.number, undefined]), 38 | buttonConfiguration: PropTypes.object.isRequired, 39 | } 40 | 41 | constructor(props, context) { 42 | super(props, context); 43 | const { animation } = this.props.buttonConfiguration; 44 | 45 | this.state = { rippleValue: new Animated.Value(0) }; 46 | 47 | this.animationObject = new TabAnimations(animation); 48 | this.animationStyle = this.animationObject.getAnimatedStyle(); 49 | } 50 | 51 | onPressedIn = (onPress) => { 52 | this.animationObject.callAnimations(); 53 | Animated.timing(this.state.rippleValue, { 54 | toValue: 1, 55 | duration: animationDuration, 56 | easing: Easing.bezier(0.0, 0.0, 0.1, 1), 57 | }).start(() => { 58 | this.state.rippleValue.setValue(0); 59 | }); 60 | /* eslint-disable react/no-string-refs */ 61 | if (this.refs.image_ref) { 62 | this.refs.image_ref.callAnimations(); 63 | } 64 | onPress(); 65 | } 66 | 67 | renderRippleView(buttonConfig) { 68 | const { rippleValue } = this.state; 69 | const { activeTintColor, animation } = buttonConfig; 70 | if (animation.includes('ripple')) { 71 | return ( 72 | 73 | 86 | 87 | ); 88 | } 89 | return null; 90 | } 91 | 92 | renderIconImage = (buttonConfiguration) => { 93 | const { 94 | active, 95 | tabIcon, 96 | iconStyle, 97 | activeTintColor, 98 | inactiveTintColor, 99 | } = buttonConfiguration; 100 | 101 | if (tabIcon) { 102 | if (typeof tabIcon === 'function') { 103 | return React.createElement(tabIcon, { ...buttonConfiguration, ref: 'image_ref' }); 104 | } 105 | const imageStyle = iconStyle 106 | ? [styles.iconImage, iconStyle] 107 | : [styles.iconImage]; 108 | if (active) { 109 | imageStyle.push({ tintColor: activeTintColor }); 110 | } else { 111 | imageStyle.push({ tintColor: inactiveTintColor }); 112 | } 113 | 114 | return ( 115 | 120 | ); 121 | } 122 | return null; 123 | } 124 | 125 | renderTitleText = (buttonConfig) => { 126 | const { 127 | title, 128 | active, 129 | showTitle, 130 | activeTintColor, 131 | inactiveTintColor, 132 | textStyle, 133 | textActiveStyle, 134 | textInactiveStyle, 135 | } = buttonConfig; 136 | 137 | const titleTextStyle = textStyle 138 | ? [styles.titleText, textStyle] 139 | : [styles.titleText]; 140 | 141 | if (active) { 142 | if (textActiveStyle) { 143 | titleTextStyle.push(textActiveStyle); 144 | } 145 | if (activeTintColor) { 146 | titleTextStyle.push({ color: activeTintColor }); 147 | } 148 | } else { 149 | if (textInactiveStyle) { 150 | titleTextStyle.push(textInactiveStyle); 151 | } 152 | if (inactiveTintColor) { 153 | titleTextStyle.push({ color: inactiveTintColor }); 154 | } 155 | } 156 | 157 | if (showTitle) { 158 | return ( 159 | 163 | {title} 164 | 165 | ); 166 | } 167 | return null; 168 | } 169 | 170 | renderAnimatedButton = (onButtonPress, buttonConfiguration) => { 171 | const { viewWidth } = this.props; 172 | if (Platform.OS === 'ios') { 173 | return ( 174 | 175 | {this.renderRippleView(buttonConfiguration)} 176 | this.onPressedIn(onButtonPress, null)} 178 | style={styles.touchableView} 179 | > 180 | 186 | {this.renderIconImage(buttonConfiguration)} 187 | 188 | 189 | {this.renderTitleText(buttonConfiguration)} 190 | 191 | 192 | 193 | ); 194 | } 195 | return ( 196 | 197 | {this.renderRippleView(buttonConfiguration)} 198 | 209 | {this.renderIconImage(buttonConfiguration)} 210 | 211 | this.onPressedIn(onButtonPress)} 213 | style={[ 214 | styles.touchableView, 215 | styles.touchableAnimatedView, 216 | { 217 | width: viewWidth / 1.2, 218 | left: (viewWidth - (viewWidth / 1.2)) / 2, 219 | }, 220 | ]} 221 | > 222 | 223 | {this.renderTitleText(buttonConfiguration)} 224 | 225 | 226 | 227 | ); 228 | } 229 | 230 | renderUnanimatedButton = (onButtonPress, buttonConfiguration) => ( 231 | 232 | 236 | 237 | {this.renderIconImage(buttonConfiguration)} 238 | 239 | 240 | {this.renderTitleText(buttonConfiguration)} 241 | 242 | 243 | 244 | ) 245 | 246 | render() { 247 | const { buttonConfiguration, onPress } = this.props; 248 | return ( 249 | buttonConfiguration.animated 250 | ? this.renderAnimatedButton(onPress, buttonConfiguration) 251 | : this.renderUnanimatedButton(onPress, buttonConfiguration) 252 | ); 253 | } 254 | } 255 | 256 | export default TabButton; 257 | -------------------------------------------------------------------------------- /src/TabIcons/TuneViewIcon/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Animated, View, Platform } from 'react-native'; 4 | 5 | import styles from './styles'; 6 | 7 | const leftInterpolation = { 8 | inputRange: [0, 0.5, 1], 9 | outputRange: Platform.OS === 'ios' ? [0, -8, 0] : [0, 22, 0], 10 | }; 11 | 12 | const rightInterpolation = { 13 | inputRange: [0, 0.5, 1], 14 | outputRange: Platform.OS === 'ios' ? [0, 8, 0] : [0, -22, 0], 15 | }; 16 | 17 | class TuneViewIcon extends Component { 18 | static propTypes = { 19 | active: PropTypes.bool.isRequired, 20 | activeTintColor: PropTypes.string.isRequired, 21 | inactiveTintColor: PropTypes.string.isRequired, 22 | } 23 | 24 | constructor() { 25 | super(); 26 | this.state = { 27 | buttonsAnimation: new Animated.Value(0), 28 | }; 29 | } 30 | 31 | callAnimations() { 32 | Animated.spring( 33 | this.state.buttonsAnimation, 34 | { 35 | toValue: 1, 36 | tension: 1, 37 | friction: 2, 38 | }, 39 | ).start(() => this.state.buttonsAnimation.setValue(0)); 40 | } 41 | 42 | render() { 43 | const { 44 | active, 45 | activeTintColor, 46 | inactiveTintColor, 47 | } = this.props; 48 | 49 | const viewStyle = active 50 | ? { backgroundColor: activeTintColor } 51 | : { backgroundColor: inactiveTintColor }; 52 | 53 | return ( 54 | 55 | 56 | 57 | 66 | 67 | 68 | 69 | 78 | 79 | 80 | 81 | 90 | 91 | 92 | ); 93 | } 94 | } 95 | export default TuneViewIcon; 96 | -------------------------------------------------------------------------------- /src/TabIcons/TuneViewIcon/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from 'react-native'; 2 | 3 | const styles = StyleSheet.create({ 4 | container: { 5 | height: 24, 6 | width: 26, 7 | paddingTop: 3, 8 | backgroundColor: 'transparent', 9 | }, 10 | rowContainer: { 11 | height: 4, 12 | width: '100%', 13 | justifyContent: 'center', 14 | alignItems: 'center', 15 | marginVertical: 1, 16 | paddingHorizontal: 1, 17 | backgroundColor: 'transparent', 18 | }, 19 | rowTrack: { 20 | height: 2, 21 | width: '90%', 22 | backgroundColor: 'grey', 23 | borderRadius: 1, 24 | }, 25 | buttonLeft: { 26 | height: 4, 27 | width: 4, 28 | position: 'absolute', 29 | left: 4, 30 | top: 0, 31 | backgroundColor: 'grey', 32 | borderRadius: 1, 33 | }, 34 | buttonRight: { 35 | height: 4, 36 | width: 4, 37 | position: 'absolute', 38 | right: 4, 39 | top: 0, 40 | backgroundColor: 'grey', 41 | borderRadius: 1, 42 | }, 43 | }); 44 | 45 | export default styles; 46 | -------------------------------------------------------------------------------- /src/TabIcons/index.js: -------------------------------------------------------------------------------- 1 | import Message from '../../resources/img/messageIcon.png'; 2 | import Social from '../../resources/img/socialIcon.png'; 3 | import Lever from '../../resources/img/leverIcon.png'; 4 | import Tune from '../../resources/img/tuneIcon.png'; 5 | import Bell from '../../resources/img/bellIcon.png'; 6 | import TuneView from './TuneViewIcon'; 7 | 8 | export default { 9 | TuneView, 10 | Message, 11 | Social, 12 | Lever, 13 | Bell, 14 | Tune, 15 | }; 16 | -------------------------------------------------------------------------------- /src/TabNavigation/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import NavigatorView from './navigatorView'; 4 | import TabButton from '../TabButton'; 5 | 6 | const defaultScreenOptions = { 7 | showTitle: true, 8 | animated: true, 9 | buttonView: TabButton, 10 | activeTintColor: '#0579fc', 11 | inactiveTintColor: '#818692', 12 | 13 | tabIcon: null, 14 | iconStyle: null, 15 | textStyle: null, 16 | textActiveStyle: null, 17 | textInactiveStyle: null, 18 | 19 | animation: [], 20 | }; 21 | 22 | const defaultNavigationConfig = { 23 | lazy: false, 24 | screenOptions: defaultScreenOptions, 25 | }; 26 | 27 | const screensState = { 28 | state: { name: '' }, 29 | screens: { }, 30 | }; 31 | 32 | function generateStateInitInformation(router, config) { 33 | const routesEntries = Object.entries(router); 34 | const buttonsArray = routesEntries.map((item) => { 35 | const screenKey = item[0]; 36 | const screenOptions = { 37 | ...config.screenOptions, 38 | ...item[1].screenOptions, 39 | active: false, 40 | key: screenKey, 41 | }; 42 | 43 | if (config.defaultRoute === screenKey) { 44 | screenOptions.active = true; 45 | } 46 | 47 | if (!screenOptions.title) { 48 | screenOptions.title = screenKey; 49 | } 50 | 51 | return { ...screenOptions }; 52 | }); 53 | 54 | return buttonsArray; 55 | } 56 | 57 | function navigateToScreen(screenName, currentState) { 58 | let nextScreenName = screenName; 59 | const nextState = currentState.map((item) => { 60 | const isCurrentScreen = item.key === screenName; 61 | const nextItem = { 62 | ...item, 63 | active: isCurrentScreen, 64 | }; 65 | if (isCurrentScreen) { 66 | nextScreenName = item.key; 67 | } 68 | return nextItem; 69 | }); 70 | return { 71 | state: nextState, 72 | screenName: nextScreenName, 73 | }; 74 | } 75 | 76 | function createTabNavigator(router, navConfig) { 77 | const navigatorRouter = router; 78 | const tmpNavConfig = { ...navConfig }; 79 | const navigatorConfig = { 80 | ...defaultNavigationConfig, 81 | ...tmpNavConfig, 82 | defaultRoute: tmpNavConfig.defaultRoute || Object.keys(router)[0], 83 | screenOptions: { 84 | ...defaultNavigationConfig.screenOptions, 85 | ...tmpNavConfig.screenOptions, 86 | }, 87 | }; 88 | screensState.state.name = navigatorConfig.defaultRoute; 89 | const routesEntries = Object.entries(router); 90 | 91 | if (navigatorConfig.lazy) { 92 | screensState.screens[screensState.state.name] = React.createElement( 93 | router[screensState.state.name].screen, 94 | ); 95 | } else { 96 | routesEntries.forEach((item) => { 97 | screensState.screens[item[0]] = React.createElement(item[1].screen); 98 | }); 99 | } 100 | 101 | const routeState = generateStateInitInformation(navigatorRouter, navigatorConfig); 102 | 103 | class TabNavigation extends React.Component { 104 | constructor() { 105 | super(); 106 | this.routeState = routeState; 107 | } 108 | 109 | navigateTo = (navToScreen) => { 110 | const { state, screenName } = navigateToScreen(navToScreen, this.routeState); 111 | this.routeState = state; 112 | screensState.state.name = screenName; 113 | 114 | if (!screensState.screens[screensState.state.name]) { 115 | screensState.screens[screensState.state.name] = React.createElement( 116 | router[screensState.state.name].screen, 117 | ); 118 | } 119 | this.forceUpdate(); 120 | } 121 | 122 | getScreenFromRoute = () => { 123 | 124 | } 125 | 126 | render() { 127 | return ( 128 | 133 | ); 134 | } 135 | } 136 | 137 | return TabNavigation; 138 | } 139 | 140 | export default createTabNavigator; 141 | -------------------------------------------------------------------------------- /src/TabNavigation/navigatorView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { View } from 'react-native'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import BarPanel from '../BarPanel'; 6 | 7 | import styles from './styles'; 8 | 9 | class NavigatorView extends Component { 10 | static propTypes = { 11 | // screen: 12 | navigate: PropTypes.func.isRequired, 13 | buttonsConfiguration: PropTypes.array.isRequired, 14 | } 15 | 16 | render() { 17 | const { 18 | screen, 19 | navigate, 20 | buttonsConfiguration, 21 | } = this.props; 22 | const ScreenView = screen; 23 | return ( 24 | 25 | 26 | {ScreenView} 27 | 28 | 32 | 33 | ); 34 | } 35 | } 36 | 37 | export default NavigatorView; 38 | -------------------------------------------------------------------------------- /src/TabNavigation/styles.js: -------------------------------------------------------------------------------- 1 | import { StyleSheet, Platform, Dimensions } from 'react-native'; 2 | 3 | const { height, width } = Dimensions.get('screen'); 4 | 5 | export const isIOSX = Platform.OS === 'ios' 6 | && (height === 812 || width === 812); 7 | 8 | const styles = StyleSheet.create({ 9 | navigationContainer: { 10 | flex: 1, 11 | }, 12 | screenContainer: { 13 | flex: 1, 14 | backgroundColor: 'rgb(255, 255, 255)', 15 | ...Platform.select({ 16 | ios: { 17 | marginBottom: isIOSX ? 75 : 60, 18 | }, 19 | android: { 20 | marginBottom: 60, 21 | }, 22 | }), 23 | }, 24 | }); 25 | 26 | export default styles; 27 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as TabNavigation } from './TabNavigation'; 2 | export { default as TabButton } from './TabButton'; 3 | export { default as BarPanel } from './BarPanel'; 4 | export { default as TabIcons } from './TabIcons'; 5 | --------------------------------------------------------------------------------