feat: add <Drawer /> component (#72)

This commit is contained in:
Ahmed Elhanafy
2016-11-20 20:28:47 +02:00
committed by Satyajit Sahoo
parent c3a3e21196
commit d0c9a4ad4e
8 changed files with 337 additions and 12 deletions

View File

@@ -2,32 +2,87 @@
import Exponent from 'exponent';
import React, { Component } from 'react';
import {
View,
StyleSheet,
Platform,
} from 'react-native';
import {
NavigationProvider,
StackNavigation,
} from '@exponent/ex-navigation';
import { Colors, ThemeProvider } from 'react-native-paper';
import { Colors, ThemeProvider, Drawer } from 'react-native-paper';
import Router from './src/Router';
const DrawerItems = [
{ label: 'Inbox', icon: 'inbox', key: 0 },
{ label: 'Starred', icon: 'star', key: 1 },
{ label: 'Sent mail', icon: 'send', key: 2 },
{ label: 'A very long title that will be truncated', icon: 'delete', key: 3 },
{ label: 'No Icon', key: 4 },
];
class App extends Component {
state = {
open: false,
drawerItemIndex: 0,
}
_handleOpenDrawer = () => this.setState({ open: true })
_handleCloseDrawer = () => this.setState({ open: false })
_setDrawerItem = index => this.setState({ drawerItemIndex: index })
_renderDrawerItems = () => {
return (
<View style={styles.drawerContent}>
<Drawer.Section label='Subheader'>
{DrawerItems.map((props, index) => (
<Drawer.Item
{...props}
key={props.key}
active={this.state.drawerItemIndex === index}
onPress={() => this._setDrawerItem(index)}
/>))}
</Drawer.Section>
</View>
);
};
render() {
return (
<ThemeProvider>
<NavigationProvider router={Router}>
<StackNavigation
defaultRouteConfig={{
navigationBar: {
title: 'Examples',
tintColor: Colors.white,
backgroundColor: Colors.indigo500,
},
}}
initialRoute={Router.getRoute('home')}
/>
<Drawer
onOpen={this._handleOpenDrawer}
onClose={this._handleCloseDrawer}
open={this.state.open}
content={this._renderDrawerItems()}
>
<StackNavigation
defaultRouteConfig={{
navigationBar: {
title: 'Examples',
tintColor: Colors.white,
backgroundColor: Colors.indigo500,
},
}}
initialRoute={Router.getRoute('home')}
/>
</Drawer>
</NavigationProvider>
</ThemeProvider>
);
}
}
const styles = StyleSheet.create({
drawerContent: {
flex: 1,
marginTop: Platform.OS === 'android' ? 25 : 22,
},
});
Exponent.registerRootComponent(App);

23
index.js Normal file
View File

@@ -0,0 +1,23 @@
/* @flow */
export { default as ThemeProvider } from './src/core/ThemeProvider';
export { default as withTheme } from './src/core/withTheme';
export * as Colors from './src/styles/colors';
export { default as Button } from './src/components/Button';
export { default as Card } from './src/components/Card';
export { default as Checkbox } from './src/components/Checkbox';
export { default as Paper } from './src/components/Paper';
export { default as RadioButton } from './src/components/RadioButton';
export { default as TouchableRipple } from './src/components/TouchableRipple';
export { default as Drawer } from './src/components/Drawer';
export { default as Divider } from './src/components/Divider';
export { default as Caption } from './src/components/Typography/Caption';
export { default as Headline } from './src/components/Typography/Headline';
export { default as Paragraph } from './src/components/Typography/Paragraph';
export { default as Subheading } from './src/components/Typography/Subheading';
export { default as Title } from './src/components/Typography/Title';
export { default as Text } from './src/components/Typography/Text';

View File

@@ -39,6 +39,7 @@
"react-native-vector-icons": "~3.0.0"
},
"dependencies": {
"color": "^0.11.4"
"color": "^0.11.4",
"react-native-drawer": "^2.3.0"
}
}

View File

@@ -0,0 +1,142 @@
/* @flow */
import RNDrawer from 'react-native-drawer';
import React, {
Component,
PropTypes,
} from 'react';
import { View, StyleSheet } from 'react-native';
import DrawerItem from './DrawerItem';
import DrawerSection from './DrawerSection';
import { white } from '../../styles/colors';
type Props = {
children?: any;
content?: any;
locked?: boolean;
onClose?: Function;
onOpen?: Function;
open?: boolean;
swipeRatio?: number;
style?: any;
side?: string;
width?: number;
}
type DefaultProps = {
locked: boolean;
open: boolean;
swipeRatio: number;
side: string;
}
class Drawer extends Component<DefaultProps, Props, void> {
static Item = DrawerItem;
static Section = DrawerSection;
static propTypes = {
children: PropTypes.node.isRequired,
content: PropTypes.node.isRequired,
/*
Specifies the lock mode of the drawer. The drawer can be locked in 2 states:
- false, meaning that the drawer will respond (open/close) to touch gestures
- true, meaning that the drawer won't respond to touch gestures but it can be open by passing prop `open`
*/
locked: PropTypes.bool,
onClose: PropTypes.func,
onOpen: PropTypes.func,
open: PropTypes.bool,
/* Ratio of screen width that is valid for the start of a pan open action */
swipeRatio: PropTypes.number,
width: PropTypes.number,
style: View.propTypes.style,
side: PropTypes.oneOf([ 'left', 'right' ]),
}
static defaultProps = {
side: 'left',
locked: false,
open: false,
swipeRatio: 0.05,
}
_root: any;
_tweenHandler = ratio => ({
main: {
opacity: 1,
},
mainOverlay: {
opacity: ratio / 2,
backgroundColor: 'black',
},
})
_handleClose = () => {
if (this.props.onClose) {
this.props.onClose();
}
global.requestAnimationFrame(() => {
if (this.props.open === false) {
return;
} else {
this._root.open();
}
});
}
_handleOpen = () => {
if (this.props.onOpen) {
this.props.onOpen();
}
global.requestAnimationFrame(() => {
if (this.props.open === true) {
return;
} else {
this._root.close();
}
});
}
_calculateOpenDrawerOffset = viewport => this.props.width ?
(viewport.width - this.props.width) :
(viewport.width - ((viewport.width * 80) / 100))
render(): ?React.Element<any> {
const {
children,
content,
style,
side,
locked,
open,
swipeRatio,
} = this.props;
return (
<RNDrawer
{...this.props}
ref={c => (this._root = c)}
tapToClose
captureGestures
negotiatePan
acceptPan={!locked}
type='overlay'
tweenEasing='easeInQuad'
content={content}
open={open}
openDrawerOffset={this._calculateOpenDrawerOffset}
panOpenMask={swipeRatio}
onClose={this._handleClose}
onOpen={this._handleOpen}
tweenHandler={this._tweenHandler}
side={side}
styles={{ drawer: StyleSheet.flatten({ backgroundColor: white }, style) }}
>
{children}
</RNDrawer>
);
}
}
export default Drawer;

View File

@@ -0,0 +1,61 @@
/* @flow */
import color from 'color';
import React, {
PropTypes,
} from 'react';
import { View, Text, StyleSheet } from 'react-native';
import Icon from '.././Icon';
import TouchableRipple from '../TouchableRipple';
import { grey300 } from '../../styles/colors';
import withTheme from '../../core/withTheme';
import type { Theme } from '../../types/Theme';
type Props = {
icon?: string;
label: string;
active?: boolean;
onPress?: Function;
theme: Theme;
}
const DrawerItem = ({ icon, label, active, onPress, theme, ...props }: Props) => {
const { colors } = theme;
const labelColor = active ? colors.primary : color(colors.text).alpha(0.87).rgbaString();
const iconColor = active ? colors.primary : color(colors.text).alpha(0.54).rgbaString();
const fontFamily = theme.fonts.medium;
const labelMargin = icon ? 32 : 0;
return (
<TouchableRipple {...props} onPress={onPress}>
<View style={[ styles.wrapper, { backgroundColor: active ? grey300 : 'transparent' } ]}>
{ icon &&
<Icon
name={icon}
size={24}
color={iconColor}
/>}
<Text numberOfLines={1} style={{ color: labelColor, fontFamily, marginLeft: labelMargin, marginRight: 16 }}>{label}</Text>
</View>
</TouchableRipple>
);
};
const styles = StyleSheet.create({
wrapper: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 16,
height: 48,
},
});
DrawerItem.propTypes = {
icon: PropTypes.string,
label: PropTypes.string.isRequired,
active: PropTypes.bool,
onPress: PropTypes.func,
theme: PropTypes.object.isRequired,
};
export default withTheme(DrawerItem);

View File

@@ -0,0 +1,39 @@
/* @flow */
import color from 'color';
import React, { PropTypes } from 'react';
import { View, Text } from 'react-native';
import Divider from '../Divider';
import withTheme from '../../core/withTheme';
import type { Theme } from '../../types/Theme';
type Props = {
children: any;
label?: string;
theme: Theme;
}
const DrawerSection = ({ children, label, theme, ...props }: Props) => {
const { colors, fonts } = theme;
const labelColor = color(colors.text).alpha(0.54).rgbaString();
const fontFamily = fonts.medium;
return (
<View {...props}>
{ label &&
<View style={{ height: 40, justifyContent: 'center' }}>
<Text numberOfLines={1} style={{ color: labelColor, fontFamily, marginLeft: 16 }}>{label}</Text>
</View>}
{children}
<Divider style={{ marginVertical: 4 }} />
</View>
);
};
DrawerSection.propTypes = {
children: PropTypes.node.isRequired,
label: PropTypes.string,
theme: PropTypes.object.isRequired,
};
export default withTheme(DrawerSection);

View File

@@ -0,0 +1,3 @@
/* @flow */
export { default as default } from './Drawer';

View File

@@ -20,3 +20,4 @@ export { default as Title } from './components/Typography/Title';
export { default as Text } from './components/Typography/Text';
export { default as Divider } from './components/Divider';
export { default as Drawer } from './components/Drawer';