diff --git a/README.md b/README.md index 4292911..e480b76 100644 --- a/README.md +++ b/README.md @@ -523,6 +523,7 @@ import { StackNavigation, DrawerNavigation, DrawerNavigationItem, + DrawerNavigationChild, } from '@exponent/ex-navigation'; // Treat the DrawerNavigationLayout route like any other route -- you may want to set @@ -554,6 +555,10 @@ class DrawerNavigationLayout extends React.Component { /> + + Meta + + {this.props.title} + ); + } +} - _renderHeader = () => { +class DrawerItem extends DrawerNavigationItem { + renderIcon = (isSelected: bool) => { + let extraStyle = {marginTop: 2}; + if (this.props.icon === 'md-alert') { + extraStyle = {...extraStyle, marginLeft: -3}; + } return ( - - - + ); }; - _renderTitle = (text: string, isSelected: bool) => { + renderTitle = (isSelected: bool) => { return ( - {text} + {this.props.title} ); }; - _renderIcon = (name: string, isSelected: bool) => { - let extraStyle = {marginTop: 2}; - if (name === 'md-alert') { - extraStyle = {...extraStyle, marginLeft: -3}; - } + get selectedItemStyle() { + return styles.selectedItemStyle; + } + + get children() { + let { defaultRouteConfig } = this.props; + return ( - ); + } +} + +export default class DrawerNavigationExample extends Component { + renderHeader = () => { + return ( + + + + ); }; render() { return ( - this._renderTitle('Examples', isSelected)} - renderIcon={isSelected => this._renderIcon('md-apps', isSelected)}> - - - + + this._renderTitle('About', isSelected)} - renderIcon={isSelected => this._renderIcon('md-alert', isSelected)}> - - + icon="md-alert" + title="About" + defaultRouteConfig={{ + navigationBar: { + backgroundColor: '#0084FF', + tintColor: '#fff', + }, + }} + stack={{ + id: 'root', + initialRoute: Router.getRoute('about'), + }} + /> ); } @@ -90,6 +121,12 @@ const styles = StyleSheet.create({ width: null, resizeMode: 'cover', }, + headingText: { + paddingVertical: 10, + paddingHorizontal: 15, + color: '#777', + fontWeight: 'bold', + }, buttonTitleText: { color: '#222', fontWeight: 'bold', diff --git a/src/ExNavigation.js b/src/ExNavigation.js index c8c3cb2..6bf7c31 100644 --- a/src/ExNavigation.js +++ b/src/ExNavigation.js @@ -18,6 +18,7 @@ export { default as SlidingTabNavigationItem } from './sliding-tab/ExNavigationS export { default as DrawerNavigation } from './drawer/ExNavigationDrawer'; export { default as DrawerNavigationItem } from './drawer/ExNavigationDrawerItem'; +export { default as DrawerNavigationChild } from './drawer/ExNavigationDrawerChild'; export { default as NavigationBar } from './ExNavigationBar'; diff --git a/src/drawer/ExNavigationDrawer.js b/src/drawer/ExNavigationDrawer.js index ad77eca..7e45c1c 100644 --- a/src/drawer/ExNavigationDrawer.js +++ b/src/drawer/ExNavigationDrawer.js @@ -11,7 +11,6 @@ import { } from 'react-native'; import DrawerLayout from 'react-native-drawer-layout'; import PureComponent from '../utils/PureComponent'; -import StaticContainer from 'react-static-container'; import invariant from 'invariant'; import _ from 'lodash'; @@ -22,6 +21,7 @@ import { createNavigatorComponent } from '../ExNavigationComponents'; import ExNavigationDrawerLayout from './ExNavigationDrawerLayout'; import ExNavigationDrawerItem from './ExNavigationDrawerItem'; +import ExNavigationDrawerChild from './ExNavigationDrawerChild'; import type ExNavigationContext from '../ExNavigationContext'; export class ExNavigationDrawerContext extends ExNavigatorContext { @@ -77,7 +77,7 @@ type Props = { type State = { id: string, navigatorUID: string, - drawerItems: Array, + drawerItems: Array>, parentNavigatorUID: string, renderedItemKeys: Array, }; @@ -147,6 +147,7 @@ class ExNavigationDrawer extends PureComponent { drawerPosition: this.props.drawerPosition, width: this.props.drawerWidth, renderNavigationView: this.props.renderNavigationView, + setActiveItem: this.setActiveItem, style: [ this.props.drawerStyle, ], @@ -163,7 +164,7 @@ class ExNavigationDrawer extends PureComponent { renderContent = () => { const items = this.state.renderedItemKeys.map(key => { - return this.state.drawerItems.find(i => i.id === key); + return this.state.drawerItems.find(i => i.props.id === key); }); return ( @@ -173,26 +174,16 @@ class ExNavigationDrawer extends PureComponent { ); }; - renderItemContent(drawerItem: Object) { - if (!drawerItem.element) { - return null; - } - + renderItemContent(drawerItem: React.Element) { const navState = this._getNavigationState(); const selectedChild = navState.routes[navState.index]; - const isSelected = drawerItem.id === selectedChild.key; + const isSelected = drawerItem.props.id === selectedChild.key; - return ( - - - {drawerItem.element} - - - ); + return React.cloneElement(drawerItem, { + key: drawerItem.props.id, + isSelected, + renderTo: 'content', + }); } componentWillMount() { @@ -265,33 +256,12 @@ class ExNavigationDrawer extends PureComponent { } invariant( - child.type === ExNavigationDrawerItem, - 'All children of DrawerNavigation must be DrawerNavigationItems.', + child.type.prototype === ExNavigationDrawerChild.prototype || + child.type instanceof Object && ExNavigationDrawerChild.prototype.isPrototypeOf(child.type.prototype), + 'All children of DrawerNavigation must be DrawerNavigationChild descendant components.' ); - const drawerItemProps = child.props; - - let drawerItem = { - ..._.omit(drawerItemProps, ['children']), - }; - - if (Children.count(drawerItemProps.children) > 0) { - drawerItem.element = Children.only(drawerItemProps.children); - } - - const drawerItemOnPress = () => { - this._setActiveItem(drawerItemProps.id, index); - }; - - if (typeof drawerItemProps.onPress === 'function') { - drawerItem.onPress = drawerItem.onPress.bind(this, drawerItemOnPress); - } else { - drawerItem.onPress = drawerItemOnPress; - } - - drawerItem.onLongPress = drawerItemProps.onLongPress; - - return drawerItem; + return child; }); this.setState({ @@ -299,12 +269,12 @@ class ExNavigationDrawer extends PureComponent { }); } - _setActiveItem(id, index) { + setActiveItem = (id) => { this._getNavigatorContext().jumpToItem(id); if (typeof this.props.onPress === 'function') { this.props.onPress(id); } - } + }; toggleDrawer = () => { this._drawerLayout && this._drawerLayout.toggle(); @@ -346,11 +316,4 @@ const styles = StyleSheet.create({ itemContentOuter: { flex: 1, }, - itemContentInner: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - }, }); diff --git a/src/drawer/ExNavigationDrawerChild.js b/src/drawer/ExNavigationDrawerChild.js new file mode 100644 index 0000000..b163e3b --- /dev/null +++ b/src/drawer/ExNavigationDrawerChild.js @@ -0,0 +1,74 @@ +import React from 'react'; +import { + StyleSheet, + View, +} from 'react-native'; +import StaticContainer from 'react-static-container'; + +export type Props = { + children: React.Element, + renderTo: 'drawer' | 'content', + showsTouches: ?boolean, + isSelected: boolean, + selectedStyle: any, +}; + +export default class ExNavigationDrawerChild extends React.Component { + props: Props; + hasContent: boolean = false; + + renderDrawerItem() { + let { children } = this.props; + return React.createElement(View, null, children); + } + + renderContent() { + return null; + } + + _renderContent() { + const { isSelected } = this.props; + let content = this.renderContent(); + + if (content === null) { + return null; + } + + return ( + + + {content} + + + ); + } + + render() { + let { renderTo } = this.props; + + if (renderTo === 'drawer') { + return this.renderDrawerItem(); + } else if (renderTo === 'content') { + return this._renderContent(); + } else { + console.warn('renderTo must be "drawer" or "content"'); + return null; + } + } +} + +const styles = StyleSheet.create({ + itemContentOuter: { + flex: 1, + }, + itemContentInner: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + }, +}); diff --git a/src/drawer/ExNavigationDrawerItem.js b/src/drawer/ExNavigationDrawerItem.js index dfcdba1..4f0d26e 100644 --- a/src/drawer/ExNavigationDrawerItem.js +++ b/src/drawer/ExNavigationDrawerItem.js @@ -1,7 +1,113 @@ import React from 'react'; +import { + TouchableWithoutFeedback, + StyleSheet, + View, +} from 'react-native'; +import TouchableNativeFeedbackSafe from '@exponent/react-native-touchable-native-feedback-safe'; +import ExNavigationDrawerChild from './ExNavigationDrawerChild'; +import type { Props } from './ExNavigationDrawerChild'; + +function _set(self, name, fn) { + // Stop use of `get` from interfering with `renderTitle => ...` in subclasses + Object.defineProperty(self, name, { + value: fn, + }); +} + +export default class ExNavigationDrawerItem extends ExNavigationDrawerChild { + props: Props; + + get showsTouches() { return this.props.showsTouches; } + get renderIcon() { return this.props.renderIcon; } + get renderTitle() { return this.props.renderTitle; } + get renderRight() { return this.props.renderRight; } + get onPress() { return this.props.onPress; } + get onLongPress() { return this.props.onLongPress; } + get children() { return this.props.children; } + set showsTouches(fn) { _set(this, 'showsTouches', fn); } + set renderIcon(fn) { _set(this, 'renderIcon', fn); } + set renderTitle(fn) { _set(this, 'renderTitle', fn); } + set renderRight(fn) { _set(this, 'renderRight', fn); } + set onPress(fn) { _set(this, 'onPress', fn); } + set onLongPress(fn) { _set(this, 'onLongPress', fn); } + set children(fn) { _set(this, 'children', fn); } + + renderDrawerItem() { + let { isSelected } = this.props; + let { showsTouches, renderIcon, renderTitle, renderRight, onPress, onLongPress } = this; + const icon = renderIcon && renderIcon(isSelected); + const title = renderTitle && renderTitle(isSelected); + const rightElement = renderRight && renderRight(isSelected); + + if (showsTouches !== false) { + return ( + + + { + icon && {icon} + } + { + title && {title} + } + { + rightElement && {rightElement} + } + + + ); + } else { + return ( + + + { + icon && {icon} + } + { + title && {title} + } + { + rightElement && {rightElement} + } + + + ); + } + } + + renderContent() { + const children = this.children; + if (React.Children.count(children) > 0) { + return React.Children.only(children); + } -export default class ExNavigationDrawerItem extends React.Component { - render() { return null; } } + +const styles = StyleSheet.create({ + buttonContainer: { + flex: 1, + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + paddingVertical: 10, + paddingHorizontal: 15, + }, + elementContainer: { + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'center', + }, + rightElementContainer: { + flex: 1, + justifyContent: 'flex-end', + }, +}); diff --git a/src/drawer/ExNavigationDrawerLayout.js b/src/drawer/ExNavigationDrawerLayout.js index 51c1d4a..4d24ab3 100644 --- a/src/drawer/ExNavigationDrawerLayout.js +++ b/src/drawer/ExNavigationDrawerLayout.js @@ -4,23 +4,30 @@ import React from 'react'; import { - NativeModules, ScrollView, StyleSheet, - Text, - TouchableWithoutFeedback, View, } from 'react-native'; import DrawerLayout from 'react-native-drawer-layout'; -import TouchableNativeFeedbackSafe from '@exponent/react-native-touchable-native-feedback-safe'; + +type NavigationViewOptions = { + renderHeader: () => React.Element, + renderDrawerItems: (items: Array>) => Array>, + items: Array>, + containerStyle: Array, + scrollableContentContainerStyle: Array, +}; type Props = { renderHeader: () => React.Element, + renderNavigationView: (options:NavigationViewOptions) => React.Element, width: number, + items: Array>, children: React.Element, drawerBackgroundColor: string, drawerPosition: 'left' | 'right', selectedItem: any, + setActiveItem: (id: string) => void, }; type State = { @@ -44,7 +51,7 @@ export default class ExNavigationDrawerLayout extends React.Component { drawerBackgroundColor={this.props.drawerBackgroundColor} drawerWidth={this.props.width} drawerPosition={DrawerLayout.positions[position]} - renderNavigationView={this.props.renderNavigationView || this._renderNavigationView}> + renderNavigationView={this._renderNavigationView}> {this.props.children} ); @@ -59,73 +66,45 @@ export default class ExNavigationDrawerLayout extends React.Component { } _renderNavigationView = () => { + const renderNavigationView = this.props.renderNavigationView || this.renderNavigationView; + return renderNavigationView({ + renderHeader: this.props.renderHeader, + renderDrawerItems: this.renderDrawerItems, + items: this.props.items, + containerStyle: [styles.navigationViewContainer, this.props.style], + scrollableContentContainerStyle: [styles.navigationViewScrollableContentContainer], + }); + } + + renderNavigationView = ({renderHeader, renderDrawerItems, items, containerStyle, scrollableContentContainerStyle}:NavigationViewOptions) => { return ( - + - {this.props.renderHeader()} + {renderHeader()} - - {this._renderDrawerItems()} + + {renderDrawerItems(items)} ); } - _renderDrawerItems = () => { - if (!this.props.items) { - return null; + renderDrawerItems = (items: Array>) => { + if (!items) { + return []; } - return this.props.items.map((item, index) => { - let { renderIcon, renderTitle, renderRight } = item; - let isSelected = this.props.selectedItem === item.id; - const icon = renderIcon && renderIcon(isSelected); - const title = renderTitle && renderTitle(isSelected); - const rightElement = renderRight && renderRight(isSelected); - - if (item.showsTouches !== false) { - return ( - { this._handlePress(item); }} - onLongPress={() => { this._handleLongPress(item); }} - delayPressIn={0} - style={[isSelected ? item.selectedStyle : item.style]} - background={item.nativeFeedbackBackground}> - - { - icon && {icon} - } - { - title && {title} - } - { - rightElement && {rightElement} - } - - - ); - } else { - return ( - { this._handlePress(item); }} - onLongPress={() => { this._handleLongPress(item); }}> - - { - icon && {icon} - } - { - title && {title} - } - { - rightElement && {rightElement} - } - - - ); - } + return items.map((item, index) => { + let isSelected = this.props.selectedItem === item.props.id; + + return React.cloneElement(item, { + key: index, + isSelected, + onPress: () => { this._handlePress(item.props); }, + onLongPress: () => { this._handleLongPress(item.props); }, + renderTo: 'drawer', + }); }); } @@ -133,7 +112,11 @@ export default class ExNavigationDrawerLayout extends React.Component { // onPress and onLongPress should fire after close drawer! // _handlePress = (item: any) => { - item.onPress(); + if (item.onPress) { + item.onPress(); + } else { + this.props.setActiveItem(item.id); + } this._component.closeDrawer(); } @@ -155,21 +138,4 @@ const styles = StyleSheet.create({ navigationViewScrollableContentContainer: { paddingTop: 8, }, - buttonContainer: { - flex: 1, - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'center', - paddingVertical: 10, - paddingHorizontal: 15, - }, - elementContainer: { - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'center', - }, - rightElementContainer: { - flex: 1, - justifyContent: 'flex-end' - } });