react-native-material-bottom-navigation
Advanced tools
Comparing version 0.8.2 to 0.9.0
@@ -7,8 +7,3 @@ /** | ||
import React, { Component } from 'react' | ||
import { | ||
View, | ||
Text, | ||
StyleSheet, | ||
Animated | ||
} from 'react-native'; | ||
import { View, Text, StyleSheet, Animated } from 'react-native' | ||
@@ -21,3 +16,3 @@ type BProps = { | ||
isVisible: boolean, | ||
translateY: number, | ||
translateY: number | ||
} | ||
@@ -28,3 +23,3 @@ | ||
isVisible: true, | ||
style: {}, | ||
style: {} | ||
} | ||
@@ -38,20 +33,20 @@ | ||
createStyles() { | ||
const { size, style } = this.props; | ||
const { size, style } = this.props | ||
const container = Object.assign( | ||
{}, | ||
{ | ||
position: "absolute", | ||
position: 'absolute', | ||
width: size, | ||
height: size, | ||
borderRadius: size / 2, | ||
alignItems: "center", | ||
justifyContent: "center", | ||
backgroundColor: "#F50057", | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
backgroundColor: '#F50057', | ||
zIndex: 9999, | ||
top: 3, | ||
left: "50%", | ||
left: '50%', | ||
marginLeft: 15 | ||
}, | ||
style.container | ||
); | ||
) | ||
@@ -61,5 +56,5 @@ const text = Object.assign( | ||
{ | ||
color: "#fff", | ||
fontWeight: "500", | ||
fontSize: 12, | ||
color: '#fff', | ||
fontWeight: '500', | ||
fontSize: 12 | ||
}, | ||
@@ -71,3 +66,3 @@ style.text | ||
container, | ||
text, | ||
text | ||
}) | ||
@@ -77,6 +72,6 @@ } | ||
render() { | ||
const { children, text, isVisible, translateY } = this.props; | ||
const styles = this.createStyles(); | ||
const { children, text, isVisible, translateY } = this.props | ||
const styles = this.createStyles() | ||
if (!isVisible) { | ||
return null; | ||
return null | ||
} | ||
@@ -86,6 +81,3 @@ | ||
<Animated.View | ||
style={[ | ||
styles.container, | ||
{ transform: [{ translateY: translateY }] }, | ||
]} | ||
style={[styles.container, { transform: [{ translateY: translateY }] }]} | ||
> | ||
@@ -92,0 +84,0 @@ <Text style={styles.text}>{text}</Text> |
@@ -21,3 +21,2 @@ /** | ||
type BottomNavigationProps = { | ||
@@ -53,3 +52,2 @@ activeTab: number, | ||
export default class BottomNavigation extends Component { | ||
static defaultProps: typeof defaultProps | ||
@@ -88,3 +86,3 @@ props: BottomNavigationProps | ||
UIManager.setLayoutAnimationEnabledExperimental && | ||
UIManager.setLayoutAnimationEnabledExperimental(true) | ||
UIManager.setLayoutAnimationEnabledExperimental(true) | ||
} | ||
@@ -102,5 +100,7 @@ } | ||
if (__DEV__) { | ||
console.warn('You shouldn\'t put more than 5 Tabs in the ' + | ||
'BottomNavigation. Styling may break and it\'s against the specs ' + | ||
'in the Material Design Guidelines.') | ||
console.warn( | ||
"You shouldn't put more than 5 Tabs in the " + | ||
"BottomNavigation. Styling may break and it's against the specs " + | ||
'in the Material Design Guidelines.' | ||
) | ||
} | ||
@@ -157,5 +157,6 @@ } | ||
var shifting = this.props.shifting != null | ||
? this.props.shifting | ||
: this.props.children.length > 3 | ||
var shifting = | ||
this.props.shifting != null | ||
? this.props.shifting | ||
: this.props.children.length > 3 | ||
@@ -165,10 +166,8 @@ return ( | ||
ref="navigation" | ||
style={[ { overflow: 'hidden' }, this.props.style ]} | ||
style={[{ overflow: 'hidden' }, this.props.style]} | ||
onLayout={this._handleOnLayout} | ||
> | ||
<View style={[ | ||
this.props.innerStyle, | ||
styles.container, | ||
{ backgroundColor } | ||
]}> | ||
<View | ||
style={[this.props.innerStyle, styles.container, { backgroundColor }]} | ||
> | ||
<RippleBackgroundTransition | ||
@@ -186,3 +185,3 @@ ref="backgroundRipple" | ||
/> | ||
{React.Children.map(this.props.children, (child, tabIndex) => ( | ||
{React.Children.map(this.props.children, (child, tabIndex) => | ||
React.cloneElement(child, { | ||
@@ -197,8 +196,8 @@ shifting, | ||
labelColor: child.props.labelColor || this.props.labelColor, | ||
activeLabelColor: child.props.activeLabelColor || | ||
this.props.activeLabelColor, | ||
barBackgroundColor: child.props.barBackgroundColor || | ||
this.props.backgroundColor | ||
activeLabelColor: | ||
child.props.activeLabelColor || this.props.activeLabelColor, | ||
barBackgroundColor: | ||
child.props.barBackgroundColor || this.props.backgroundColor | ||
}) | ||
))} | ||
)} | ||
</View> | ||
@@ -273,3 +272,5 @@ </View> | ||
// Make magic LayoutAnimation for next Layout Change | ||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) | ||
if (Platform.OS !== 'web') { | ||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) | ||
} | ||
@@ -276,0 +277,0 @@ // Announce that the layout will change. This will cause, that |
@@ -11,3 +11,2 @@ /** | ||
type NCProps = { | ||
@@ -28,4 +27,7 @@ // I could use react-navigation's type definitions, but I don't want to | ||
export default | ||
class NavigationComponent extends PureComponent<void, NCProps, void> { | ||
export default class NavigationComponent extends PureComponent< | ||
void, | ||
NCProps, | ||
void | ||
> { | ||
render() { | ||
@@ -72,4 +74,4 @@ // react-navigation passed props | ||
// `tabBarOptions` will be added. | ||
style={[ { height: 56 }, bnStyle || style ]} | ||
onTabChange={(index) => jumpToIndex(index)} | ||
style={[{ height: 56 }, bnStyle || style]} | ||
onTabChange={index => jumpToIndex(index)} | ||
{...bnProps} | ||
@@ -107,7 +109,15 @@ > | ||
return <Tab | ||
key={index} | ||
onPress={onPress ? () => { onPress(scene, jumpToIndex) } : null} | ||
{...tabProps} | ||
/> | ||
return ( | ||
<Tab | ||
key={index} | ||
onPress={ | ||
onPress | ||
? () => { | ||
onPress(scene, jumpToIndex) | ||
} | ||
: null | ||
} | ||
{...tabProps} | ||
/> | ||
) | ||
})} | ||
@@ -114,0 +124,0 @@ </BottomNavigation> |
@@ -11,10 +11,5 @@ /** | ||
import React, { Component } from 'react' | ||
import { | ||
View, | ||
Animated, | ||
Platform | ||
} from 'react-native' | ||
import { View, Animated, Platform } from 'react-native' | ||
import { easeOut } from './utils/easing' | ||
type PressRippleProps = { | ||
@@ -39,3 +34,2 @@ color: string, | ||
export default class PressRipple extends Component { | ||
static defaultProps: typeof defaultProps | ||
@@ -74,7 +68,7 @@ props: PressRippleProps | ||
position: 'absolute', | ||
top: this.props.y - (size/2), | ||
left: this.props.x - (size/2), | ||
top: this.props.y - size / 2, | ||
left: this.props.x - size / 2, | ||
width: size, | ||
height: size, | ||
borderRadius: size/2, | ||
borderRadius: size / 2, | ||
opacity, | ||
@@ -96,6 +90,14 @@ transform: [{ scale }] | ||
Animated.parallel([ | ||
Animated.timing(this.state.scale, | ||
{ toValue: 1, duration: 200, easing: easeOut, useNativeDriver }), | ||
Animated.timing(this.state.opacity, | ||
{ toValue: 0, duration: 300, easing: easeOut, useNativeDriver }) | ||
Animated.timing(this.state.scale, { | ||
toValue: 1, | ||
duration: 200, | ||
easing: easeOut, | ||
useNativeDriver | ||
}), | ||
Animated.timing(this.state.opacity, { | ||
toValue: 0, | ||
duration: 300, | ||
easing: easeOut, | ||
useNativeDriver | ||
}) | ||
]).start(() => { | ||
@@ -102,0 +104,0 @@ // Initial values |
@@ -8,7 +8,3 @@ /** | ||
import React, { Component } from 'react' | ||
import { | ||
View, | ||
Animated, | ||
Platform | ||
} from 'react-native' | ||
import { View, Animated, Platform } from 'react-native' | ||
import { easeOut } from './utils/easing' | ||
@@ -29,3 +25,2 @@ | ||
export default class RippleBackgroundTransition extends Component { | ||
props: RBTProps | ||
@@ -96,8 +91,8 @@ state: RBTState | ||
const testVectors = [ | ||
[ 0, 0 ], | ||
[ this.layout.width, 0 ], | ||
[ this.layout.width, this.layout.height ], | ||
[ 0, this.layout.height ] | ||
[0, 0], | ||
[this.layout.width, 0], | ||
[this.layout.width, this.layout.height], | ||
[0, this.layout.height] | ||
] | ||
const refVector = [ x, y ] | ||
const refVector = [x, y] | ||
@@ -110,3 +105,3 @@ testVectors.forEach((vector, i) => { | ||
// Note: d is now a squared value | ||
const d = dX*dX + dY*dY | ||
const d = dX * dX + dY * dY | ||
@@ -113,0 +108,0 @@ if (d > biggestDistance) biggestDistance = d |
205
lib/Tab.js
@@ -17,6 +17,5 @@ /** | ||
} from 'react-native' | ||
import Badge from "./Badge" | ||
import Badge from './Badge' | ||
import { easeInOut } from './utils/easing' | ||
const useNativeDriver = Platform.OS === 'android' | ||
@@ -39,3 +38,3 @@ | ||
badgeStyle: any, | ||
isBadgeVisible: boolean, | ||
isBadgeVisible: boolean | ||
} | ||
@@ -59,3 +58,2 @@ | ||
export default class Tab extends Component { | ||
props: TabProps | ||
@@ -69,10 +67,2 @@ state: TabState | ||
// HACK: In shifting mode, after the first animation from active to | ||
// inactive, the icon jumps down before animating to the active state | ||
// again. In order to fix this, we need to store, if it already was | ||
// active. Then we can catch that case and manually move it up before | ||
// animating. This only happens in Android, not iOS. | ||
// Is this a bug in react-native or somewhere here? | ||
this.didOnceBecameActive = props.active ? true : false | ||
this.state = { | ||
@@ -122,4 +112,4 @@ fixed: { | ||
styles.container, | ||
(this._isShifting() && active) && styles.shiftingActiveContainer, | ||
(this._isShifting() && !active) && styles.shiftingInactiveContainer | ||
this._isShifting() && active && styles.shiftingActiveContainer, | ||
this._isShifting() && !active && styles.shiftingInactiveContainer | ||
]} | ||
@@ -136,17 +126,17 @@ > | ||
_renderBadge = () => { | ||
const { badgeText, badgeSize, badgeStyle, isBadgeVisible } = this.props; | ||
const mode = this._getModeString(); | ||
const { badgeText, badgeSize, badgeStyle, isBadgeVisible } = this.props | ||
const mode = this._getModeString() | ||
if (badgeText === undefined && !isBadgeVisible) { | ||
return null; | ||
return null | ||
} | ||
return ( | ||
<Badge | ||
text={badgeText} | ||
size={badgeSize} | ||
style={badgeStyle} | ||
isVisible={isBadgeVisible} | ||
translateY={this.state[mode].iconY} | ||
/> | ||
<Badge | ||
text={badgeText} | ||
size={badgeSize} | ||
style={badgeStyle} | ||
isVisible={isBadgeVisible} | ||
translateY={this.state[mode].iconY} | ||
/> | ||
) | ||
@@ -156,4 +146,4 @@ } | ||
_renderIcon = () => { | ||
const mode = this._getModeString(); | ||
const { active, icon, activeIcon } = this.props; | ||
const mode = this._getModeString() | ||
const { active, icon, activeIcon } = this.props | ||
@@ -168,3 +158,2 @@ return ( | ||
> | ||
<View ref="_bnic" collapsable={false}> | ||
@@ -205,10 +194,26 @@ {active && activeIcon ? activeIcon : icon} | ||
Animated.parallel([ | ||
Animated.timing(this.state.fixed.iconY, | ||
{ toValue: -2, duration, easing, useNativeDriver }), | ||
Animated.timing(this.state.fixed.labelScale, | ||
{ toValue: 1, duration, easing, useNativeDriver }), | ||
Animated.timing(this.state.fixed.labelY, | ||
{ toValue: 0, duration, easing, useNativeDriver }), | ||
Animated.timing(this.state.fixed.iconOpacity, | ||
{ toValue: 1, duration, easing, useNativeDriver }) | ||
Animated.timing(this.state.fixed.iconY, { | ||
toValue: -2, | ||
duration, | ||
easing, | ||
useNativeDriver | ||
}), | ||
Animated.timing(this.state.fixed.labelScale, { | ||
toValue: 1, | ||
duration, | ||
easing, | ||
useNativeDriver | ||
}), | ||
Animated.timing(this.state.fixed.labelY, { | ||
toValue: 0, | ||
duration, | ||
easing, | ||
useNativeDriver | ||
}), | ||
Animated.timing(this.state.fixed.iconOpacity, { | ||
toValue: 1, | ||
duration, | ||
easing, | ||
useNativeDriver | ||
}) | ||
]).start() | ||
@@ -222,10 +227,26 @@ } | ||
Animated.parallel([ | ||
Animated.timing(this.state.fixed.iconY, | ||
{ toValue: 0, duration, easing, useNativeDriver }), | ||
Animated.timing(this.state.fixed.labelScale, | ||
{ toValue: 0.857, duration, easing, useNativeDriver }), | ||
Animated.timing(this.state.fixed.labelY, | ||
{ toValue: 2, duration, easing, useNativeDriver }), | ||
Animated.timing(this.state.fixed.iconOpacity, | ||
{ toValue: 0.8, duration, easing, useNativeDriver }) | ||
Animated.timing(this.state.fixed.iconY, { | ||
toValue: 0, | ||
duration, | ||
easing, | ||
useNativeDriver | ||
}), | ||
Animated.timing(this.state.fixed.labelScale, { | ||
toValue: 0.857, | ||
duration, | ||
easing, | ||
useNativeDriver | ||
}), | ||
Animated.timing(this.state.fixed.labelY, { | ||
toValue: 2, | ||
duration, | ||
easing, | ||
useNativeDriver | ||
}), | ||
Animated.timing(this.state.fixed.iconOpacity, { | ||
toValue: 0.8, | ||
duration, | ||
easing, | ||
useNativeDriver | ||
}) | ||
]).start() | ||
@@ -237,17 +258,29 @@ } | ||
// HACK: See above "didOnceBecameActive" | ||
if (Platform.OS === 'android') { | ||
if (this.didOnceBecameActive) this.state.shifting.iconY.setValue(0) | ||
this.didOnceBecameActive = true | ||
} | ||
Animated.parallel([ | ||
Animated.timing(this.state.shifting.iconY, | ||
{ toValue: 0, duration: 266, easing, useNativeDriver }), | ||
Animated.timing(this.state.shifting.iconOpacity, | ||
{ toValue: 1, duration: 266, easing, useNativeDriver }), | ||
Animated.timing(this.state.shifting.labelOpacity, | ||
{ toValue: 1, duration: 183, delay: 83, easing, useNativeDriver }), | ||
Animated.timing(this.state.shifting.labelScale, | ||
{ toValue: 1, duration: 183, delay: 83, easing, useNativeDriver }) | ||
Animated.timing(this.state.shifting.iconY, { | ||
toValue: 0, | ||
duration: 266, | ||
easing, | ||
useNativeDriver | ||
}), | ||
Animated.timing(this.state.shifting.iconOpacity, { | ||
toValue: 1, | ||
duration: 266, | ||
easing, | ||
useNativeDriver | ||
}), | ||
Animated.timing(this.state.shifting.labelOpacity, { | ||
toValue: 1, | ||
duration: 183, | ||
delay: 83, | ||
easing, | ||
useNativeDriver | ||
}), | ||
Animated.timing(this.state.shifting.labelScale, { | ||
toValue: 1, | ||
duration: 183, | ||
delay: 83, | ||
easing, | ||
useNativeDriver | ||
}) | ||
]).start() | ||
@@ -260,10 +293,26 @@ } | ||
Animated.parallel([ | ||
Animated.timing(this.state.shifting.iconY, | ||
{ toValue: 8, duration: 266, easing, useNativeDriver }), | ||
Animated.timing(this.state.shifting.labelOpacity, | ||
{ toValue: 0, duration: 83, easing, useNativeDriver }), | ||
Animated.timing(this.state.shifting.labelScale, | ||
{ toValue: 0.857, duration: 83, easing, useNativeDriver }), | ||
Animated.timing(this.state.shifting.iconOpacity, | ||
{ toValue: 0.8, duration: 266, easing, useNativeDriver }) | ||
Animated.timing(this.state.shifting.iconY, { | ||
toValue: 8, | ||
duration: 266, | ||
easing, | ||
useNativeDriver | ||
}), | ||
Animated.timing(this.state.shifting.labelOpacity, { | ||
toValue: 0, | ||
duration: 83, | ||
easing, | ||
useNativeDriver | ||
}), | ||
Animated.timing(this.state.shifting.labelScale, { | ||
toValue: 0.857, | ||
duration: 83, | ||
easing, | ||
useNativeDriver | ||
}), | ||
Animated.timing(this.state.shifting.iconOpacity, { | ||
toValue: 0.8, | ||
duration: 266, | ||
easing, | ||
useNativeDriver | ||
}) | ||
]).start() | ||
@@ -282,7 +331,10 @@ } | ||
// so call it's function to handle that. | ||
this.props.onTabPress({ | ||
tabIndex: this.props.tabIndex, | ||
barBackgroundColor: this.props.barBackgroundColor, | ||
iconRef: this.refs._bnic | ||
}, { updateActiveTab, forceAnimation }) | ||
this.props.onTabPress( | ||
{ | ||
tabIndex: this.props.tabIndex, | ||
barBackgroundColor: this.props.barBackgroundColor, | ||
iconRef: this.refs._bnic | ||
}, | ||
{ updateActiveTab, forceAnimation } | ||
) | ||
} | ||
@@ -308,3 +360,2 @@ | ||
const styles = StyleSheet.create({ | ||
@@ -316,7 +367,13 @@ container: { | ||
paddingTop: 8, | ||
paddingBottom: 10, | ||
paddingBottom: Platform.OS !== 'web' ? 10 : 0, | ||
paddingLeft: 12, | ||
paddingRight: 12, | ||
backgroundColor: 'transparent', | ||
position: "relative", | ||
position: 'relative', | ||
...(Platform.OS !== 'web' | ||
? {} | ||
: { | ||
transitionDuration: '0.5s', | ||
transitionProperty: 'all' | ||
}) | ||
}, | ||
@@ -340,3 +397,3 @@ shiftingInactiveContainer: { | ||
textAlign: 'center', | ||
includeFontPadding: false, | ||
...(Platform.OS !== 'web' ? { includeFontPadding: false } : {}), | ||
textAlignVertical: 'center', | ||
@@ -343,0 +400,0 @@ justifyContent: 'flex-end', |
@@ -1,2 +0,2 @@ | ||
Copyright 2017 Timo Mämecke | ||
Copyright 2017 - 2018 Timo Mämecke | ||
@@ -3,0 +3,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
{ | ||
"name": "react-native-material-bottom-navigation", | ||
"version": "0.8.2", | ||
"description": "JS Implementation of the Material Design Guidelines' Bottom Navigation for react-native", | ||
"version": "0.9.0", | ||
"description": "A highly accurate JS Implementation of the Material Design Bottom Navigation Component for react-native", | ||
"scripts": { | ||
"lint": "eslint ." | ||
}, | ||
"main": "index.js", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/timomeh/react-native-material-bottom-navigation.git" | ||
"url": | ||
"git+https://github.com/timomeh/react-native-material-bottom-navigation.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/timomeh/react-native-material-bottom-navigation/issues" | ||
"url": | ||
"https://github.com/timomeh/react-native-material-bottom-navigation/issues" | ||
}, | ||
@@ -25,6 +30,14 @@ "author": "Timo Mämecke <maemecketimo@gmail.com>", | ||
], | ||
"types": "./index.d.ts", | ||
"devDependencies": { | ||
"babel-eslint": "^8.2.2", | ||
"babel-jest": "18.0.0", | ||
"babel-preset-react-native": "1.9.1", | ||
"eslint": "^4.18.1", | ||
"eslint-config-prettier": "^2.9.0", | ||
"eslint-plugin-prettier": "^2.6.0", | ||
"eslint-plugin-react": "^7.7.0", | ||
"eslint-plugin-react-native": "^3.2.1", | ||
"jest": "18.1.0", | ||
"prettier": "^1.10.2", | ||
"react-test-renderer": "~15.4.0" | ||
@@ -31,0 +44,0 @@ }, |
320
README.md
# Material Design Bottom Navigation for react-native | ||
A highly accurate Bottom Navigation Component for react-native, based on [Material Guidelines' Bottom Navigation](https://material.io/guidelines/components/bottom-navigation.html). | ||
A highly accurate JS Implementation of the Material Design Bottom Navigation Component for react-native, following the [Material Design Guidelines](https://material.io/guidelines/components/bottom-navigation.html). | ||
* Support for iOS and Android (it's programmed only in JavaScript) | ||
* Supports iOS, Android and react-native-web (no native dependencies, only JS) | ||
* Uses those dope Ripple Transitions between two background colors | ||
* Follows the Material Design Guidelines | ||
* Switches automatically between Fixed Navigation (up to 3 tabs) and Shifting Navigation (3 - 5 tabs) | ||
* No dependencies | ||
* Includes a Badge | ||
* Support for [react-navigation](https://reactnavigation.org) | ||
* Zero dependencies | ||
* Supports Badges | ||
* Supports Types for Flow and TypeScript | ||
* Supports [react-navigation](https://reactnavigation.org) | ||
The Bottom navigation looks lovely. That's probably the reason why you're here. Using a Bottom Navigation is a good choice. More and more apps are switching from a Burger Menu and/or [Tabs](https://material.io/guidelines/components/tabs.html) to a Bottom Navigation, including Google Apps. | ||
## Background | ||
The Material Design Bottom Navigation looks lovely. That's probably the reason why you're here. Using a Bottom Navigation is a good choice. More and more apps are switching from a Burger Menu and/or [Tabs](https://material.io/guidelines/components/tabs.html) to a Bottom Navigation, including Google Apps. | ||
**Fixed Bottom Navigation** | ||
@@ -23,15 +24,15 @@ | ||
**Behind the Android System Navigation Bar** | ||
## Table of Contents | ||
![behind navigation bar](.github/behind-nav-bar.gif) | ||
* [Install](#install) | ||
* [Usage](#usage) | ||
* [Reference](#reference) | ||
* [BottomNavigation](#bottomnavigation) | ||
* [Tab](#tab) | ||
* [Usage with react-navigation](#usage-with-react-navigation) | ||
* [Behind the Navigation Bar](#behind-the-navigation-bar) | ||
* [Contribute](#contribute) | ||
* [Authors](#authors) | ||
* [License](#license) | ||
- [Install](#install) | ||
- [But how? (Usage)](#but-how) | ||
- [Configuration](#configuration) | ||
- [Behind the Navigation Bar](#behind-the-navigation-bar) | ||
- [Usage for react-navigation](#usage-for-react-navigation) | ||
- [Roadmap](#roadmap) | ||
- [LICENSE](#license) | ||
## Install | ||
@@ -47,10 +48,7 @@ | ||
## Usage | ||
## But how? | ||
This is an example for a Bottom Navigation with 4 Tabs, each Tab has its own background color. | ||
In this example, I used [react-native-vector-icons](https://github.com/oblador/react-native-vector-icons) as Icon Components. You can use whatever Component you want. | ||
```jsx | ||
```js | ||
import React, { Component } from 'react' | ||
@@ -66,4 +64,11 @@ import BottomNavigation, { Tab } from 'react-native-material-bottom-navigation' | ||
rippleColor="white" | ||
style={{ height: 56, elevation: 8, position: 'absolute', left: 0, bottom: 0, right: 0 }} | ||
onTabChange={(newTabIndex) => alert(`New Tab at position ${newTabIndex}`)} | ||
style={{ | ||
height: 56, | ||
elevation: 8, | ||
position: 'absolute', | ||
left: 0, | ||
bottom: 0, | ||
right: 0 | ||
}} | ||
onTabChange={newTabIndex => alert(`New Tab at position ${newTabIndex}`)} | ||
> | ||
@@ -96,137 +101,106 @@ <Tab | ||
## Configuration | ||
You can find more examples in the [`examples/`](examples) directory. | ||
Don't skip this part. You will be happy to know about all the good stuff you can configure here. | ||
## Reference | ||
**Note:** If you are searching for more customization options, like label styles for fonts/positioning/..., they are *intentionally* not supported. More and more customizations would be actively against the Material Design Guidelines, and I want to encourage you to follow the Guidelines. | ||
### `BottomNavigation` | ||
### BottomNavigation | ||
* **`style`** Object. **Required.** | ||
Style will be directly applied to the component. Use this to set the height of the BottomNavigation (should be 56), to position it, to add shadow and border. | ||
| Prop | Description | Type | Default | | ||
|------|--------------|------|--------| | ||
| **`activeTab`** | Index of the preselected Tab, starting from 0. | `number` | `0` | | ||
| **`labelColor`** | Text Color of the Tab's Label. Can be overwritten by the Tab itself. | `string` | `rgba(0, 0, 0, 0.54)` | | ||
| **`activeLabelColor`** | Text Color of the active Tab's Label. Can be overwritten by the Tab itself. | `string` | `labelColor` | | ||
| **`rippleColor`** | Color of the small Ripple Effect when the Tab will be pressed. Has opacity of `0.12`. | `string` | `black` | | ||
| **`backgroundColor`** | Background color of the Bottom Navigation. Can be overwritten by the Tab itself, to achieve different background colors for each active Tab. | `string` | `white` | | ||
| **`onTabChange`** | Function to be called when a Tab was pressed and changes into active state. Will be called with parameters `(newTabIndex, oldTabIndex) => {}`. | `function` | `noop` | | ||
| **`style`** | **Required.** Style will be directly applied to the component. Use this to set the height of the BottomNavigation (should be 56), to position it, to add shadow and border. The only pre-set rule is `overflow: hidden`. | `object` | **Required.** | | ||
| **`innerStyle`** | All tabs are wrapped in another container. Use this to add styles to this container. The main reason why you would want to use this is to put the Navigation behind the Android System Navigation Bar. See below for an example on how to achieve this. | `object` | – | | ||
| **`shifting`** | Turn manually on/off shifting mode. | `boolean` | `true` if > 3 Tabs, otherwise `false` | | ||
* `activeLabelColor` String. Default: value of `labelColor` | ||
Text Color of the active Tab's Label. Can be overwritten by the Tab itself. | ||
**Hints:** | ||
* `activeTab` Number. Default: `0` | ||
Index of the currently active Tab. | ||
- Elevation should be `8` | ||
- Height should be `56` | ||
- Width should be 100% | ||
- Follow all specs defined in the [Official Guidelines](https://material.io/guidelines/components/bottom-navigation.html#bottom-navigation-specs) | ||
* `backgroundColor` String. Default: `white` | ||
Background color of the Bottom Navigation. Can be overwritten by the Tab itself, to achieve different background colors for each active Tab. | ||
* `innerStyle` Object. | ||
All tabs are wrapped in another container. Use this to add styles to this container. The main reason why you would want to use this is to put the Navigation behind the Android System Navigation Bar. Check _[Behind the Navigation Bar](#behind-the-navigation-bar)_ for an example. | ||
### Tab | ||
* `labelColor` String. Default: `rgba(0, 0, 0, 0.54)` | ||
Text Color of the Tab's Label. Can be overwritten by the Tab itself. | ||
| Prop | Description | Type | Default | | ||
|------|--------------|------|--------| | ||
| **`icon`** | **Required.** Component to render as icon. Should have height and width of `24`. | `ReactElement<*>` | **Required.** | | ||
| **`activeIcon`** | Component to render as icon when the Tab is active. Should have height and width of `24`. Use this to change the color of the icon. | `ReactElement<*>` | `icon` | | ||
| **`label`** | **Required.** Text of the Label. | `string` | **Required.** | | ||
| **`labelColor`** | Text Color of the Label. | `string` | `labelColor` of BottomNavigation | | ||
| **`activeLabelColor`** | Text Color of the Label when the Tab is active. | `string` | `activeLabelColor` of BottomNavigation | | ||
| **`barBackgroundColor`** | Background color for the whole component, if the tab is active. | `string` | `backgroundColor` of BottomNavigation | | ||
| **`onPress`** | Function to be called when the Tab was pressed. **When you use this, the pressed tab won't be active automatically. You need to set it to active by updating `BottomNavigation.activeTab`.** This function will be called with the parameter `(newTabIndex) => {}` | `function` | – | | ||
| **`badgeText`** | Text for the tab's badge. **The badge will be hidden if no badgeText is passed. isBadgeVisible can be used to override this**. | `string` | - | | ||
| **`badgeSize`** | Size of the badge. Will be used to calculate the height, width, and border radius (height: size, width: size, borderRadius: size/2) | `number` | 20 | | ||
| **`badgeStyle`** | Style for the badge. `badgeStyle.container` will be used to determine the badge's container style, and `badgeStyle.text` will be used to determine the badge's text style | `object` | `{ container: { position: "absolute", width: 20, height: 20, borderRadius: 20 / 2, alignItems: "center", justifyContent: "center", backgroundColor: "#F50057", zIndex: 9999, top: 3, left: "50%", marginLeft: 15 }, text: { color: "#fff", fontWeight: "500", fontSize: 12 } }` | | ||
| **`isBadgeVisible`** | Determines if the badge is visible or not | `boolean` | - | | ||
* `onTabChange` Function. Arguments: `(newTabIndex, oldTabIndex)` | ||
Function to be called when a Tab was pressed and changes into active state. | ||
## Behind the Navigation Bar | ||
* `rippleColor` String. Default: `black` | ||
Color of the small Ripple Effect when the Tab will be pressed. Has opacity of `0.12`. | ||
In the Material Design Guidelines you can see examples with the Bottom Navigation behind the Software Navigation Bar. That looks pretty sweet. In theory, that's pretty simple. In practice there's a problem: Not every device has a visible Navigation Bar. If someone has hardware buttons on his phone, the Navigation Bar is usually hidden. As of now, we can't simply detect if it's visible. If you don't detect it and just add the following code, the BottomNavigation will have a huge padding-bottom on devices without a Navigation Bar. | ||
* `shifting` Boolean. Default: `true` when >= 4 Tabs, otherwise `false` | ||
Turn shifting manually on/off. | ||
See [Issue #28](https://github.com/timomeh/react-native-material-bottom-navigation/issues/28) for more informations with an initial proposal by @keeleycarrigan. | ||
### `Tab` | ||
However, if you know what you're doing, you only need to adjust a few things: | ||
* **`icon`** ReactElement. **Required.** | ||
Component to render as icon. Should have height and width of `24`. | ||
**Step 1.** In order to make the System Navigation translucent, you have to add this to `android/app/src/main/res/values/styles.xml`: | ||
* **`label`** String. **Required.** | ||
Text of the Label. | ||
```xml | ||
<!-- Customize your theme here. --> | ||
<item name="android:navigationBarColor">@android:color/transparent</item> | ||
<item name="android:windowTranslucentNavigation">true</item> | ||
``` | ||
* `activeIcon` ReactElement. | ||
Component to render as icon when the Tab is active. Should have height and width of `24`. Can be used to change to color of the icon. | ||
**Step 2.** The System Navigation has a height of 48dp. The Bottom Navigation should be 56dp tall. This makes a total height of 104. Use `innerStyle` to push the tabs above the System Navigation without pushing the whole Bottom Navigation above it. | ||
* `activeLabelColor` String. Default: value of `BottomNavigation.activeLabelColor` | ||
Text Color of the Label when the Tab is active. | ||
```jsx | ||
<BottomNavigation | ||
style={{ height: 104, ... }} | ||
innerStyle={{ paddingBottom: 48 }} | ||
> | ||
``` | ||
* `badgeSize` Number. Default: `20` | ||
Size of the badge. | ||
**Step 3.** You're done! | ||
* `badgeStyle` Object. | ||
* `badgeStyle.container` Object. | ||
Style of the badge itself. | ||
* `badgeStyle.text` Object. | ||
Style of the badge inner text. | ||
## Usage for [react-navigation](https://reactnavigation.org) | ||
* `badgeText` String. | ||
Text for the tab's badge. The badge will be hidden if no badgeText is passed. Can be overridden by `isBadgeVisible`. | ||
This package includes a Component to plug into react-navigation. It is as configurable as the standalone version. To achieve this, it uses a separate configuration inside `tabBarOptions`. You can only set those configurations for the Bottom Navigation inside the `TabNavigatorConfig` of `TabNavigator()` – **not inside `static navigationOptions` or inside the `RouteConfigs`**. | ||
* `barBackgroundColor` String. | ||
Background color of the `BottomNavigation`, when the tab is active. | ||
The following example will explain everything you need to get started. | ||
* `onPress` Function. Arguments: `(newTabIndex)` | ||
Function to be called when the Tab was pressed. When you use this, the pressed tab won't be active automatically. You need to set it to active by setting `BottomNavigation.activeTab`. | ||
```jsx | ||
* `isBadgeVisible` Boolean. | ||
Determines if the badge is visible. | ||
import React from 'react' | ||
import { NavigationComponent } from 'react-native-material-bottom-navigation' | ||
import { TabNavigator } from 'react-navigation' | ||
import { AppRegistry } from 'react-native'; | ||
* `labelColor` String. Default: `BottomNavigation.labelColor` | ||
Text Color of the Label. | ||
class MoviesAndTV extends React.Component { | ||
static navigationOptions = { | ||
tabBarLabel: 'Movies & TV', | ||
tabBarIcon: () => (<Icon size={24} color="white" name="tv" />) | ||
} | ||
### Usage with [react-navigation](https://reactnavigation.org) | ||
render() { ... } | ||
} | ||
This package includes a Component called `NavigationComponent` to plug into react-navigation's `tabBarComponent`. | ||
class Music extends React.Component { | ||
static navigationOptions = { | ||
tabBarLabel: 'Music', | ||
tabBarIcon: () => (<Icon size={24} color="white" name="music-note" />) | ||
} | ||
```js | ||
import { NavigationComponent } from 'react-native-material-bottom-navigation' | ||
render() { ... } | ||
} | ||
class Newsstand extends React.Component { | ||
static navigationOptions = { | ||
tabBarLabel: 'Newsstand', | ||
tabBarIcon: () => (<Icon size={24} color="white" name="Newsstand" />) | ||
} | ||
render() { ... } | ||
} | ||
const MyApp = TabNavigator({ | ||
MoviesAndTV: { screen: MoviesAndTV }, | ||
Music: { screen: Music }, | ||
Newsstand: { screen: Newsstand } | ||
}, { | ||
tabBarComponent: NavigationComponent, | ||
tabBarPosition: 'bottom', | ||
tabBarOptions: { | ||
bottomNavigationOptions: { | ||
labelColor: 'white', | ||
rippleColor: 'white', | ||
tabs: { | ||
MoviesAndTV: { | ||
barBackgroundColor: '#37474F' | ||
}, | ||
Music: { | ||
barBackgroundColor: '#00796B' | ||
}, | ||
Newsstand: { | ||
barBackgroundColor: '#EEEEEE', | ||
labelColor: '#434343', // like in the standalone version, this will override the already specified `labelColor` for this tab | ||
activeLabelColor: '#212121', | ||
activeIcon: <Icon size={24} color="#212121" name="newsstand" /> | ||
const MyApp = TabNavigator( | ||
{ | ||
MoviesAndTV: { screen: MoviesAndTV }, | ||
Music: { screen: Music }, | ||
Newsstand: { screen: Newsstand } | ||
}, | ||
{ | ||
tabBarComponent: NavigationComponent, | ||
tabBarPosition: 'bottom', | ||
tabBarOptions: { | ||
bottomNavigationOptions: { | ||
labelColor: 'white', | ||
backgroundColor: 'red', | ||
rippleColor: 'white', | ||
tabs: { | ||
MoviesAndTV: { | ||
barBackgroundColor: '#37474F', | ||
labelColor: 'black' | ||
}, | ||
Music: { | ||
/* ... */ | ||
}, | ||
Newsstand: { | ||
/* ... */ | ||
} | ||
} | ||
@@ -236,66 +210,66 @@ } | ||
} | ||
}) | ||
AppRegistry.registerComponent('MyApp', () => MyApp) | ||
) | ||
``` | ||
### [TabNavigatorConfig](https://reactnavigation.org/docs/navigators/tab#TabNavigatorConfig) | ||
Put the configurations for the `BottomNavigation` inside `bottomNavigationOptions`. Each Tab can also have configurations, which are inside `bottomNavigationOptions.tabs.YourTabName`. | ||
- `tabBarComponent`: Use `NavigationComponent` provided by `react-native-material-bottom-navigation`. | ||
- `tabBarPosition`: Use `bottom`. | ||
- `tabBarOptions`: react-navigation's configuration of the tab bar. | ||
You can only set those configurations for the Bottom Navigation inside the `TabNavigatorConfig` of `TabNavigator()` – Those custom options can't be put in `static navigationOptions` or inside the `RouteConfigs`. | ||
### Behind the Navigation Bar | ||
### tabBarOptions | ||
![behind navigation bar](.github/behind-nav-bar.gif) | ||
The only options, which will affect the Bottom Navigation, are the following: | ||
In the Material Design Guidelines you can see examples with the Bottom Navigation behind the Software Navigation Bar. That looks pretty sweet. In theory, that's pretty simple. In practice there's a problem: Not every device has a visible Navigation Bar. If someone has hardware buttons on his phone, the Navigation Bar is usually hidden. As of now, we can't simply detect if it's visible. If you don't detect it and just add the following code, the BottomNavigation will have a huge padding-bottom on devices without a Navigation Bar. | ||
- `style`: Corresponds to the `style` prop of [`BottomNavigation`](#BottomNavigation). If no height is specified, it will use `height: 56`. This way you don't need any styling in most cases. | ||
- `bottomNavigationOptions`: The options for the Bottom Navigation, see below. | ||
See [Issue #28](https://github.com/timomeh/react-native-material-bottom-navigation/issues/28) for more informations with a possible solution. | ||
However, if you know what you're doing, you only need to follow two simple steps: | ||
### bottomNavigationOptions | ||
<details> | ||
<summary>Step 1</summary> | ||
In order to make the System Navigation translucent, you have to add this to `android/app/src/main/res/values/styles.xml`: | ||
All options of [`BottomNavigation`](#BottomNavigation) are available. They behave like the options in the standalone version, including fallback- and default-behaviour. | ||
```xml | ||
<!-- Customize your theme here. --> | ||
<item name="android:navigationBarColor">@android:color/transparent</item> | ||
<item name="android:windowTranslucentNavigation">true</item> | ||
``` | ||
- **`labelColor`** | ||
- **`activeLabelColor`** | ||
- **`rippleColor`** | ||
- **`backgroundColor`** | ||
- **`style`**: If specified, `tabBarOptions.style` won't be used. | ||
- **`innerStyle`** | ||
- **`shifting`** | ||
- **`tabs`**: Configuration for the tabs, see below. | ||
</details> | ||
*Note: `activeTab` and `onTabChange` don't have any effect, since this is handled by react-navigation.* | ||
<details> | ||
<summary>Step 2</summary> | ||
The System Navigation has a height of 48dp. The Bottom Navigation should be 56dp tall. This makes a total height of 104. Use `innerStyle` to push the tabs above the System Navigation without pushing the whole Bottom Navigation above it. | ||
```js | ||
<BottomNavigation | ||
style={{ height: 104, ... }} | ||
innerStyle={{ paddingBottom: 48 }} | ||
> | ||
{/* ... */} | ||
</BottomNavigation> | ||
``` | ||
### tabs | ||
You're done! | ||
Each tab can be configured by its key from `RouteConfigs`. *If you take a look at the example, you will see that `MoviesAndTV`, `Music` and `Newsstand` correspond to each other.* | ||
</details> | ||
- **`tab`** is an object with `{ [routeKey]: tabOptions }` | ||
## Contribute | ||
### tabOptions | ||
Contributions are always welcome. You can contribute by [opening an issue](https://github.com/timomeh/react-native-material-bottom-navigation/issues/new) or by submitting PRs. | ||
All options of [`Tab`](#Tab) are available. They behave like the options in the standalone version, including fallback- and default-behaviour. | ||
Please note that this project is released with a Contributor [Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. | ||
- **`icon`**: If not specified, the icon inside `static navigationOptions.tabBar` of the scene will be used. | ||
- **`activeIcon`** | ||
- **`label`**: If not specified, the label inside `static navigationOptions.tabBar` of the scene will be used. | ||
- **`labelColor`** | ||
- **`activeLabelColor`** | ||
- **`barBackgroundColor`** | ||
## Authors | ||
**Author** | ||
### Why don't you use all the options provided by react-navigation? | ||
* Timo Mämecke ([GitHub](https://github.com/timomeh), [Twitter](https://twitter.com/timomeh)) | ||
At the time I developed this, react-navigation was in an early beta stage. It wasn't easy to get those options and add new options. I could only access the configs inside `tabBarOptions`, hence everything is stored there. | ||
**Contributors** | ||
## Roadmap | ||
See [Contributors List](https://github.com/timomeh/react-native-material-bottom-navigation/contributors). Thanks to everyone! | ||
Check if they are any new features announced in the [Issues](https://github.com/timomeh/react-native-material-bottom-navigation/issues). | ||
## License | ||
## [LICENSE](LICENSE.md) | ||
MIT | ||
[MIT](LICENSE.md), © 2017 - 2018 Timo Mämecke |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
55133
21
1390
11
271