From 1dbf79e89c362f9bde0928bc79a85043b162a7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jir=CC=8Ci=CC=81=20Ota=CC=81hal?= Date: Tue, 6 Dec 2016 08:42:04 +0100 Subject: [PATCH] Fix bottom navigation component - moves BottomNavigationAction under BottomNavigation.Action - makes styles global, moves them to getTheme.js - adds ability to use action without label - adds missing paddingTop - adds missing comments - fixes lint --- .../BottomNavigation.react.js | 84 ++++++++++----- .../BottomNavigationAction.react.js | 101 ++++++++++++++++++ .../BottomNavigationAction.react.js | 68 ------------ src/BottomNavigationAction/index.js | 1 - src/index.js | 1 + src/styles/getTheme.js | 29 ++++- 6 files changed, 185 insertions(+), 99 deletions(-) create mode 100644 src/BottomNavigation/BottomNavigationAction.react.js delete mode 100644 src/BottomNavigationAction/BottomNavigationAction.react.js delete mode 100644 src/BottomNavigationAction/index.js diff --git a/src/BottomNavigation/BottomNavigation.react.js b/src/BottomNavigation/BottomNavigation.react.js index 1eaa5af4..134b2512 100644 --- a/src/BottomNavigation/BottomNavigation.react.js +++ b/src/BottomNavigation/BottomNavigation.react.js @@ -1,43 +1,71 @@ -import React, { Component, PropTypes, View, Text, Dimensions } from 'react-native'; +import React, { PropTypes, PureComponent } from 'react'; +import { View, Text } from 'react-native'; -import getPlatformElevation from '../styles/getPlatformElevation'; +import BottomNavigationAction from './BottomNavigationAction.react'; const propTypes = { - children: PropTypes.node.isRequired, - style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), + /** + * The key of selected/active tab + */ + active: PropTypes.string, + /** + * BottomNavigation.Action nodes + */ + children: PropTypes.node.isRequired, + /** + * Inline style of bottom navigation + */ + style: PropTypes.shape({ + container: View.propTypes.style, + }), +}; +const defaultProps = { + style: {}, }; - -const defaultProps = {}; - const contextTypes = { - uiTheme: PropTypes.object.isRequired, + uiTheme: PropTypes.object.isRequired, }; +function getStyles(props, context) { + const { bottomNavigation } = context.uiTheme; + + const local = {}; -class BottomNavigation extends Component { - render() { - const { width } = Dimensions.get('window'); - const { palette } = this.context.uiTheme; - - const internalStyles = { - bottomNavigation: { - width: width, - height: 56, - flexDirection: 'row', - backgroundColor: palette.canvasColor, - ...getPlatformElevation(8), - } + return { + container: [ + bottomNavigation.container, + local.container, + props.style.container, + ], }; +} +/** +* Component for bottom navigation +* https://material.google.com/components/bottom-navigation.html +*/ - return( - - {this.props.children} - - ); - } -}; +class BottomNavigation extends PureComponent { + render() { + const { active, children } = this.props; + const styles = getStyles(this.props, this.context); + + return ( + + {React.Children.map( + children, + child => React.cloneElement(child, { + ...child.props, + active: child.key === active, + }) + )} + + ); + } +} BottomNavigation.propTypes = propTypes; BottomNavigation.defaultProps = defaultProps; BottomNavigation.contextTypes = contextTypes; +BottomNavigation.Action = BottomNavigationAction; + export default BottomNavigation; diff --git a/src/BottomNavigation/BottomNavigationAction.react.js b/src/BottomNavigation/BottomNavigationAction.react.js new file mode 100644 index 00000000..a4ceada8 --- /dev/null +++ b/src/BottomNavigation/BottomNavigationAction.react.js @@ -0,0 +1,101 @@ +import React, { PureComponent, PropTypes } from 'react'; +import { StyleSheet, View, Text } from 'react-native'; + +import RippleFeedback from '../RippleFeedback'; +import Icon from '../Icon'; + + +const propTypes = { + /** + * Will be rendered above the label as a content of the action. + */ + icon: PropTypes.string.isRequired, + /** + * Will be rendered under the icon as a content of the action. + */ + label: PropTypes.string, + /** + * True if the action is active (for now it'll be highlight by primary color) + */ + active: PropTypes.bool.isRequired, + /** + * Callback for on press event. + */ + onPress: PropTypes.func, + /** + * Inline style of bottom navigation + */ + style: PropTypes.shape({ + container: View.propTypes.style, + active: Text.propTypes.style, + disabled: Text.propTypes.style, + }), + +}; +const defaultProps = { + active: false, + disabled: false, + style: {}, +}; +const contextTypes = { + uiTheme: PropTypes.object.isRequired, +}; + +function getStyles(props, context) { + const { bottomNavigationAction, palette } = context.uiTheme; + + const local = {}; + + if (props.active) { + local.container = { paddingTop: 6 }; + local.icon = { color: palette.primaryColor }; + local.label = { color: palette.primaryColor, fontSize: 14 }; + } + + if (!props.label) { + local.container = { paddingTop: 16, paddingBottom: 16 }; + } + + return { + container: [ + bottomNavigationAction.container, + local.container, + props.style.container, + ], + icon: [ + bottomNavigationAction.icon, + local.icon, + props.style.icon, + ], + label: [ + bottomNavigationAction.label, + local.label, + props.style.label, + ], + }; +} + + +class BottomNavigationAction extends PureComponent { + render() { + const { icon, label, onPress } = this.props; + + const styles = getStyles(this.props, this.context); + const color = StyleSheet.flatten(styles.icon).color; + + return ( + + + + {label} + + + ); + } +} + +BottomNavigationAction.propTypes = propTypes; +BottomNavigationAction.defaultProps = defaultProps; +BottomNavigationAction.contextTypes = contextTypes; + +export default BottomNavigationAction; diff --git a/src/BottomNavigationAction/BottomNavigationAction.react.js b/src/BottomNavigationAction/BottomNavigationAction.react.js deleted file mode 100644 index d1a2a1eb..00000000 --- a/src/BottomNavigationAction/BottomNavigationAction.react.js +++ /dev/null @@ -1,68 +0,0 @@ -import React, { Component, PropTypes, View, Text } from 'react-native'; - -import Icon from '../Icon'; -import RippleFeedback from '../RippleFeedbacks'; - -const propTypes = { - label: PropTypes.string.isRequired, - iconName: PropTypes.string.isRequired, - style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), - isActive: PropTypes.bool, - onPress: PropTypes.func.isRequired, -}; - -const defaultProps = {}; - -const contextTypes = { - uiTheme: PropTypes.object.isRequired, -}; - -class BottomNavigationAction extends Component { - render() { - const { style, iconName, label, isActive, onPress } = this.props; - const { palette } = this.context.uiTheme; - - const internalStyles = { - container: { - flex: 1, - minWidth: 80, - maxWidth: 168, - height: 56, - }, - icon: { - textAlign: 'center', - marginTop: isActive ? 6 : 8, - }, - label: { - paddingLeft: 12, - paddingRight: 12, - paddingBottom: 10, - fontSize: isActive ? 14 : 12, - textAlign: 'center', - color: isActive ? palette.primaryTextColor : palette.secondaryTextColor, - } - }; - - return( - - - - { label } - - - ); - } -}; - -BottomNavigationAction.propTypes = propTypes; -BottomNavigationAction.defaultProps = defaultProps; -BottomNavigationAction.contextTypes = contextTypes; - -export default BottomNavigationAction; diff --git a/src/BottomNavigationAction/index.js b/src/BottomNavigationAction/index.js deleted file mode 100644 index 85fe9fbd..00000000 --- a/src/BottomNavigationAction/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './BottomNavigationAction.react'; diff --git a/src/index.js b/src/index.js index e0307d03..c994edc3 100644 --- a/src/index.js +++ b/src/index.js @@ -8,6 +8,7 @@ export { default as ActionButton } from './ActionButton'; export { default as Avatar } from './Avatar'; export { default as Badge } from './Badge'; export { default as Button } from './Button'; +export { default as BottomNavigation } from './BottomNavigation'; export { default as Card } from './Card'; export { default as Checkbox } from './Checkbox'; export { default as Dialog } from './Dialog'; diff --git a/src/styles/getTheme.js b/src/styles/getTheme.js index c6e9e778..d2765784 100644 --- a/src/styles/getTheme.js +++ b/src/styles/getTheme.js @@ -140,6 +140,33 @@ export default function getTheme(theme, ...more) { color: palette.disabledTextColor, }, }, theme.buttonDisabled)), + bottomNavigation: StyleSheet.create(merge({ + container: { + flexDirection: 'row', + height: 56, + backgroundColor: palette.canvasColor, + borderTopColor: palette.borderColor, + borderTopWidth: StyleSheet.hairlineWidth, + ...getPlatformElevation(8), + }, + }, theme.bottomNavigation)), + bottomNavigationAction: StyleSheet.create(merge({ + container: { + flex: 1, + alignItems: 'center', + maxWidth: 168, + minWidth: 80, + paddingBottom: 12, + paddingTop: 8, + paddingLeft: 12, + paddingRight: 12, + }, + label: { + fontSize: 12, + textAlign: 'center', + color: palette.secondaryTextColor, + }, + }, theme.bottomNavigationAction)), card: StyleSheet.create(merge({ container: { backgroundColor: palette.canvasColor, @@ -406,8 +433,6 @@ export default function getTheme(theme, ...more) { }, }, theme.toolbarSearchActive)), }, baseTheme); - - //TODO(Zino Hofmann): Add BottomNaviagtion component to uiTheme. return theme; }