├── .gitignore
├── README.md
├── components
├── button.js
├── container.js
├── icon.js
└── navbar.js
├── index.js
├── package.json
├── styles.js
└── utils.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NavbarNative
2 | A **fully customizable** Navbar component for React-Native.
3 | #### It works for both iOS and Android!
4 |
5 | 
6 |
7 | 
8 |
9 | ### Content
10 | - [Installation](#installation)
11 | - [Exported components](#exported-components)
12 | - [Getting started](#getting-started)
13 | - [Images as title](#image-as-title)
14 | - [Transparent navbar](#transparent-navbar)
15 | - [Container API](#container-api)
16 | - [Navbar API](#navbar-api)
17 | - [Demo](#demo)
18 |
19 | ### Installation
20 | ```bash
21 | npm i navbar-native --save
22 | ```
23 |
24 | ### Pay attention
25 | This package depends on the beautiful [Vector Icons for React Native](https://github.com/oblador/react-native-vector-icons).
26 |
27 | After installing NavbarNative, in order to have **icons working**, please follow instructions about [HOW TO INSTALL AND LINK VECTOR ICONS](https://github.com/oblador/react-native-vector-icons) in your project.
28 |
29 | ### Exported components
30 | This package exports two main components:
31 |
32 | - **Container** - a container component to use as the first component in a __render()__ method. It accepts the "Navbar" component and the rest of the page content.
33 | - **Navbar** - the components which generates the bar on top.
34 |
35 | ### Helper components
36 |
37 | - **Icon** - a Vector Icons for React Native wrapper
38 |
39 | ### Getting started
40 | Basically, the Navbar component accepts a **title** prop and **left** and/or **right** objects (or array of objects) which describe each button that the navbar has to render in the specific position.
41 |
42 | #### Using icons
43 | In order to use the correct set of icons, please use **ios-** prefix in _icon_ prop name for iOS and **md-** prefix for Android.
44 |
45 | The following chunk of code shows a typical **iOS** NavbarNative usage:
46 |
47 | ```js
48 | import React, { Component } from 'react';
49 | import { View } from 'react-native';
50 |
51 | import { Container, Navbar } from 'navbar-native';
52 |
53 | class ReactNativeProject extends Component {
54 | render() {
55 | return (
56 |
57 | {alert('Go back!')}
63 | }}
64 | right={[{
65 | icon: "ios-search",
66 | onPress: () => {alert('Search!')}
67 | },{
68 | icon: "ios-menu",
69 | onPress: () => {alert('Toggle menu!')}
70 | }]}
71 | />
72 | ... other stuff ...
73 |
74 | );
75 | }
76 | }
77 | ```
78 |
79 | ### Image as a title
80 |
81 | 
82 |
83 | You can also use a **remote** or **local** image instead of the text title:
84 |
85 | ```js
86 | class ReactNativeEmpty extends Component {
87 | render() {
88 | return (
89 |
90 | {alert('Go back!')}
107 | }}
108 | right={[{
109 | icon: "ios-search",
110 | onPress: () => {alert('Search!')}
111 | },{
112 | icon: "ios-menu",
113 | onPress: () => {alert('Toggle menu!')}
114 | }]}
115 | />
116 |
117 | );
118 | }
119 | }
120 | ```
121 |
122 | ### Image as background
123 |
124 | 
125 |
126 | Images can be used in **background** also:
127 |
128 | ```js
129 | class ReactNativeEmpty extends Component {
130 | render() {
131 | return (
132 |
133 | {alert('Go back!')},
152 | style:{color: 'white'}
153 | }}
154 | right={[{
155 | icon: "ios-search",
156 | iconColor: "white",
157 | onPress: () => {alert('Search!')}
158 | },{
159 | icon: "ios-menu",
160 | iconColor: "white",
161 | onPress: () => {alert('Toggle menu!')}
162 | }]}
163 | />
164 |
165 | );
166 | }
167 | }
168 | ```
169 |
170 | ### Transparent Navbar
171 |
172 | Do you need a **transparent** navbar and a full-page content beneath it? No problem! We've got you covered...
173 |
174 | Just set `bgColor="transparent"` and `theme="dark"` and you can achieve something like this:
175 |
176 | 
177 |
178 | ### Using badges
179 |
180 | 
181 |
182 | ```js
183 | export default class ReactNativeEmpty extends Component {
184 | render() {
185 |
186 | const left = {
187 | role: 'menu',
188 | badge: {
189 | value: 4,
190 | bgColor: '#ffcc00',
191 | textColor: 'black'
192 | }
193 | };
194 |
195 | return (
196 |
197 |
198 |
199 | );
200 | }
201 | }
202 | ```
203 |
204 | ### Container API
205 | - **bgColor** - (String def. '#ffffff') - Background color for the Container, the one you see overscrolling
206 | - **data** - (Array of strings or Array of Objects opt.) - data source for ListView
207 | - **row** - (Function opt.) - A function that renders the single row element in ListView (accepts 'rowData', 'sectionID')
208 | - **style** - (Object opt.) - Custom styles for the container
209 | - **loading** - (Object opt.) - Prop to use in order to trigger the included loading screen [SPINNER INSTALLATION INSTRUCTIONS](https://github.com/maxs15/react-native-spinkit)
210 | - **spinner** - (String def. 'ThreeBounce') - Type of spinner animation from [HERE](https://github.com/maxs15/react-native-spinkit)
211 | - **spinnerColor** - (String def. '#ffffff') - Color of the spinner
212 | - **spinnerSize** - (Number def. 50) - Size of the spinner
213 | - **bgColor** - (String def. 'rgba(0,0,0,.8)') - Color to apply in the background
214 | - **message** - (String opt.) - Loading text message to display
215 | - **messageColor** - (String def. '#ffffff') - Color of the loading text message
216 | - **styleContainer** - (Object opt.) - Additional style for the loading screen
217 | - **styleText** - (Object opt.) - Additional style for the loading text
218 | - **type** - ('scroll' or 'list' def. 'scroll') - How to render Container children content
219 | - **height** - (Number def. screen height) - Set the height of the container
220 |
221 | ### Navbar API
222 | - **theme** - ('light' or 'dark' - def. 'light' iOS / 'dark' Android) - Base theme for the NavigationBar
223 | - **title** - (String or Component opt.) - The title element. Component needs to be styled accordingly.
224 | - **titleColor** - (String opt.) - The title string color
225 | - **bgColor** - (String def. light: ios #f2f2f2 android #f5f5f5 dark: ios #2b2b2b android #212121 ) - NavigationBar's background color
226 | - **image** - (Object opt.) - Local/remote image instead of the title
227 | - **source** - (require(String) for local or String for remote uri) - Local/remote image location
228 | - **type** - ('local' or 'remote' def. 'local') - Origin of the image
229 | - **resizeMode** - ('cover', 'contain', 'stretch', 'repeat', 'center' def. 'cover' local - 'contain' remote)
230 | - **style** - (Object opt.) - Additional styles for image title
231 | - **imageBackground** - (Object opt.) - Local/remote image in navbar background
232 | - **source** - (String) - Local/remote image location
233 | - **type** - ('local' or 'remote' def. 'local') - Origin of the image
234 | - **resizeMode** - ('cover', 'contain', 'stretch', 'repeat', 'center' def. 'cover')
235 | - **style** - (Object opt.) - Additional styles for image background
236 | - **statusBar** - (Object opt.):
237 | - **style** - ('light-content' or 'default') - Style of StatusBar
238 | - **hidden** - (Boolean) - Show or not StatusBar
239 | - **bgColor** - (String) - StatusBar background color
240 | - **animation** - (Boolean def. true) - Animation between StatusBar transitions
241 | - **transition** - ('fade' or 'slide' def. 'fade') - Type of StatusBar transition animation when hiding it
242 | - **left / right** - (Object or Array of Objects or React component / return function):
243 | - **icon** - (String opt.) - Vector Icon's icon name
244 | - **iconFamily** - (String def. Ionicons) - Vector Icon's icon library
245 | - **iconPos** - ('left' or 'right' def. left/right position) - Icon's position towards the label
246 | - **iconSize** - (Number def. 30 ios - 28 android) - Icon's size
247 | - **iconColor** - (String def. light: ios #387afe android #707070 dark: ios #ffffff android #ffffff ) - Icon's color
248 | - **label** - (String opt.) - Button's label
249 | - **badge** - (Number, String or Object opt.)
250 | - **value** - (Number or String) - The value in the badge
251 | - **bgColor** - (String opt.) - Badge background color
252 | - **textColor** - (String opt.) - Badge text color
253 | - **position** - ('left' or 'right' def. 'right') - Badge position in the button
254 | - **onPress** - (Function) - onPress function handler
255 | - **disabled** - (Boolean def. false) - It renders a button in a disabled status
256 | - **role** - (String opt. - 'back' | 'close' | 'login' | 'menu') - Button's pre-defined aspect
257 | - **style** - (Object opt.) - Button's override styles
258 | - **style** - (Object) - Custom styles for the navbar
259 | - **user** - (Object, Bool) - Authenticated user
260 | - **elevation** - (Number) - (Android-only) Elevation of the toolbar
261 |
262 | ### Icon API
263 | - **family** - (String def. 'Ionicons') - Font family for icons
264 | - **name** - (String) - Name of the icon to show
265 | - **color** - (String def. iOS '#387afe' android '#707070') - Color of the icon
266 |
267 | ### Demo
268 |
269 | [MeteorNative](https://github.com/redbaron76/MeteorNative) is a full featured **boilerplate** which brings together **React-Native** and **Meteor js**.
270 |
271 | In this project I implement **navbar-native** in many ways and you can see in action specific usages of this package.
272 |
273 | 
274 |
--------------------------------------------------------------------------------
/components/button.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import {Text, TouchableOpacity, View} from 'react-native';
4 | import Navbar from './navbar';
5 | import styles, { theme } from '../styles';
6 |
7 | const MARGIN = 4;
8 |
9 | export default class Button extends Component {
10 |
11 | constructor(props) {
12 | super(props);
13 |
14 | this.hasIcon = false;
15 | this.hasLabel = false;
16 | }
17 |
18 | renderButtonElements() {
19 | if (Array.isArray(this.props.children)) {
20 | const buttonElements = React.Children.map(this.props.children, (child) => {
21 | if (child) {
22 | switch (true) {
23 | case (child && typeof child === 'object'):
24 | return
25 | {React.cloneElement(child, {
26 | style: [
27 | this._setIconLabelMargins(this.props),
28 | this.props.iconStyle
29 | ]
30 | })}
31 | ;
32 | case (child && typeof child == 'string'):
33 | return
39 | {child}
40 | ;
41 | }
42 | }
43 | });
44 | return (
45 |
46 | {buttonElements}
47 |
48 | );
49 | }
50 | }
51 |
52 | renderBadge() {
53 | if (
54 | (typeof this.props.badge === 'string' && !!this.props.badge) ||
55 | (typeof this.props.badge === 'number' && !!this.props.badge) ||
56 | (typeof this.props.badge === 'object' && !!this.props.badge.value)
57 | ) {
58 |
59 | let value = this.props.badge;
60 | let badgeBgColor = theme[this.props.theme].badgeBgColor;
61 | let badgeTextColor = theme[this.props.theme].badgeTextColor;
62 |
63 | let position = {};
64 | position[Navbar.RIGHT] = -7;
65 |
66 | if (typeof this.props.badge === 'object') {
67 | value = this.props.badge.value;
68 | if (this.props.badge.bgColor) badgeBgColor = this.props.badge.bgColor;
69 | if (this.props.badge.textColor) badgeTextColor = this.props.badge.textColor;
70 | if (this.props.badge.position == Navbar.LEFT) {
71 | delete(position[Navbar.RIGHT]);
72 | position[Navbar.LEFT] = -7;
73 | }
74 | }
75 |
76 | return (
77 |
82 |
86 | {value}
87 |
88 |
89 | );
90 | }
91 | }
92 |
93 | render() {
94 | this._computeIconLabel();
95 | if (!this.hasIcon && !this.hasLabel) return null;
96 | const disabled = this.props.disabled ? styles.navBarButtonDisabled : {};
97 | const buttonElements = this.renderButtonElements();
98 | return (
99 |
103 | {buttonElements}
104 | {this.renderBadge()}
105 |
106 | );
107 | }
108 |
109 | _computeIconLabel() {
110 | if (Array.isArray(this.props.children)) {
111 | const child = this.props.children;
112 | if (child[0] || child[2]) this.hasIcon = true;
113 | if (child[1]) this.hasLabel = true;
114 | }
115 | }
116 |
117 | _setButtonMargins() {
118 |
119 | switch (true) {
120 | case (this.props.btnLeft):
121 | switch (true) {
122 | case (this.hasIcon && !this.hasLabel):
123 | return {
124 | marginLeft: MARGIN,
125 | marginRight: MARGIN * 3,
126 | };
127 | default:
128 | return {
129 | marginRight: MARGIN * 2
130 | };
131 | }
132 | case (this.props.btnRight):
133 | switch (true) {
134 | case (this.hasIcon && !this.hasLabel):
135 | return {
136 | marginLeft: MARGIN * 3,
137 | marginRight: MARGIN,
138 | };
139 | default:
140 | return {
141 | marginLeft: MARGIN * 2
142 | };
143 | }
144 | }
145 | }
146 |
147 | _setIconLabelMargins(props) {
148 |
149 | switch (true) {
150 | case (this.props.btnLeft):
151 | switch (true) {
152 | case (props && props.iconPos && props.iconPos == 'right'):
153 | return {
154 | marginRight: MARGIN
155 | };
156 | case (!this.hasIcon && this.hasLabel):
157 | return {
158 | marginLeft: 0
159 | };
160 | case (this.hasIcon && !this.hasLabel):
161 | return {
162 | marginLeft: MARGIN * 2
163 | };
164 | case (this.hasIcon && this.hasLabel):
165 | return {
166 | marginLeft: MARGIN
167 | };
168 | }
169 | case (this.props.btnRight):
170 | switch (true) {
171 | case (props && props.iconPos && props.iconPos == 'left'):
172 | return {
173 | marginLeft: MARGIN
174 | };
175 | case (!this.hasIcon && this.hasLabel):
176 | return {
177 | marginRight: 0
178 | };
179 | case (this.hasIcon && !this.hasLabel):
180 | return {
181 | marginRight: MARGIN * 2
182 | };
183 | case (this.hasIcon && this.hasLabel):
184 | return {
185 | marginRight: MARGIN
186 | };
187 | }
188 | default:
189 | return {
190 | marginLeft: 0,
191 | marginRight: 0,
192 | }
193 | }
194 | }
195 |
196 | static propTypes = {
197 | style: PropTypes.oneOfType([
198 | PropTypes.object,
199 | PropTypes.array,
200 | ]),
201 | iconStyle: PropTypes.oneOfType([
202 | PropTypes.object,
203 | PropTypes.array,
204 | ]),
205 | btnLeft: PropTypes.bool,
206 | btnRight: PropTypes.bool,
207 | onPress: PropTypes.func,
208 | disabled: PropTypes.bool,
209 | theme: PropTypes.string,
210 | customStyle: PropTypes.object,
211 | badge: PropTypes.oneOfType([
212 | PropTypes.object,
213 | PropTypes.number,
214 | PropTypes.string
215 | ]),
216 | };
217 | };
218 |
--------------------------------------------------------------------------------
/components/container.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { ListView, ScrollView, View, Text, Dimensions } from 'react-native';
4 | import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
5 | import Spinner from 'react-native-spinkit';
6 | import Navbar from './navbar';
7 | import styles from '../styles';
8 | import { color, size } from '../utils';
9 |
10 | const SCROLL = 'scroll';
11 | const LIST = 'list';
12 | const PLAIN = 'plain';
13 |
14 | export default class Container extends Component {
15 |
16 | constructor(props) {
17 | super(props);
18 |
19 | const { height } = Dimensions.get('window');
20 | this.windowHeight = height;
21 | this.navbarHeight = height - (size.navBarHeight + size.statusBarHeight);
22 | this.hasStatusbar = true;
23 | this.hasNavbar = false;
24 | }
25 |
26 | renderLoadingMessage() {
27 | if (this.props.loading && this.props.loading.message) {
28 | return (
29 |
34 | {this.props.loading.message}
35 |
36 | );
37 | }
38 | return null;
39 | }
40 |
41 | renderLoading() {
42 | if (this.props.loading) {
43 | return (
44 |
50 |
51 |
56 | {this.renderLoadingMessage()}
57 |
58 |
59 | );
60 | }else{
61 | return
62 | }
63 | }
64 |
65 | renderNavbar() {
66 | this.navbarTransparent = false;
67 | const navbar = React.Children.map(this.props.children, (child) => {
68 | if (child && child.type == Navbar) {
69 | this.hasNavbar = true;
70 | if (child.props.bgColor == 'transparent') {
71 | this.navbarTransparent = true;
72 | this.hasNavbar = false;
73 | }
74 | if (child.props.statusBar && child.props.statusBar.hidden) {
75 | this.hasStatusbar = false;
76 | }
77 | return child;
78 | }
79 | });
80 | return (navbar) ? navbar : null;
81 | }
82 |
83 | _getDataSource() {
84 | if (this.props.type == LIST && this.props.data) {
85 | const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
86 | return ds.cloneWithRows(this.props.data);
87 | }
88 | }
89 |
90 | defaultListRow(rowData, sectionID) {
91 | return (
92 |
93 | {rowData}
94 |
95 | );
96 | }
97 |
98 | renderContentType(children) {
99 |
100 | let height = (this.hasNavbar) ? this.navbarHeight : this.windowHeight;
101 |
102 | if (!this.hasStatusbar) {
103 | height = height + size.statusBarHeight;
104 | }
105 |
106 | if (this.props.height) {
107 | height = this.props.height;
108 | }
109 |
110 | switch (true) {
111 | case (this.props.type == LIST && !!this.props.data):
112 | return (
113 |
119 | {children}
120 |
121 | );
122 | case (this.props.type == PLAIN):
123 | return (
124 |
127 | {children}
128 |
129 | );
130 | case (this.props.type == SCROLL):
131 | default:
132 | return (
133 |
140 | {children}
141 |
142 | );
143 | }
144 | }
145 |
146 | renderContent() {
147 | const children = React.Children.map(this.props.children, (child) => {
148 | if (child && child.type !== Navbar) {
149 | return child;
150 | }
151 | });
152 |
153 | const contentStyle = (this.navbarTransparent) ?
154 | Object.assign(
155 | {},
156 | styles.contentContainer,
157 | styles.contentAbsolute)
158 | : Object.assign(
159 | {},
160 | styles.contentContainer
161 | );
162 |
163 | let contentHeight = {};
164 | if (this.props.height) {
165 | contentHeight = {height: this.props.height};
166 | }
167 |
168 | return (
169 |
170 | {this.renderContentType(children)}
171 |
172 | );
173 | }
174 |
175 | render() {
176 | return (
177 |
178 | {this.renderLoading()}
179 | {this.renderNavbar()}
180 | {this.renderContent()}
181 |
182 | );
183 | }
184 |
185 | static SCROLL = SCROLL;
186 | static LIST = LIST;
187 | static PLAIN = PLAIN;
188 |
189 | static loadingShape = {
190 | spinner: PropTypes.string,
191 | spinnerColor: PropTypes.string,
192 | spinnerSize: PropTypes.string,
193 | bgColor: PropTypes.string,
194 | message: PropTypes.string,
195 | messageColor: PropTypes.string,
196 | styleContainer: PropTypes.object,
197 | styleText: PropTypes.object,
198 | coverNavbar:PropTypes.bool,
199 | };
200 |
201 | static arrayOfObjects = PropTypes.arrayOf(PropTypes.object);
202 | static arrayOfStrings = PropTypes.arrayOf(PropTypes.string);
203 | }
204 |
205 | Container.propTypes = {
206 | data: PropTypes.oneOfType([
207 | Container.arrayOfStrings,
208 | Container.arrayOfObjects,
209 | ]),
210 | row: PropTypes.func,
211 | contentRef:PropTypes.func,
212 | style: PropTypes.object,
213 | loading: PropTypes.oneOfType([
214 | PropTypes.bool,
215 | PropTypes.shape(Container.loadingShape),
216 | ]),
217 | type: PropTypes.oneOf([SCROLL, LIST,PLAIN]),
218 | bgColor: PropTypes.string,
219 | height: PropTypes.number,
220 | };
221 |
--------------------------------------------------------------------------------
/components/icon.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import Ionicons from 'react-native-vector-icons/Ionicons';
5 | import Entypo from 'react-native-vector-icons/Entypo';
6 | import FontAwesome from 'react-native-vector-icons/FontAwesome';
7 | import Foundation from 'react-native-vector-icons/Foundation';
8 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
9 | import Octicons from 'react-native-vector-icons/Octicons';
10 | import Zocial from 'react-native-vector-icons/Zocial';
11 |
12 | import styles, { theme } from '../styles';
13 | import { size } from '../utils';
14 |
15 | export default class Icon extends Component {
16 |
17 | constructor(props) {
18 | super(props);
19 |
20 | this.theme = (this.props.theme) ? this.props.theme : 'light';
21 | }
22 |
23 | componentWillMount() {
24 | switch(this.props.family) {
25 | case 'Ionicons':
26 | this.Icon = Ionicons;
27 | break;
28 | case 'Entypo':
29 | this.Icon = Entypo;
30 | break;
31 | case 'FontAwesome':
32 | this.Icon = FontAwesome;
33 | break;
34 | case 'Foundation':
35 | this.Icon = Foundation;
36 | break;
37 | case 'MaterialIcons':
38 | this.Icon = MaterialIcons;
39 | break;
40 | case 'Octicons':
41 | this.Icon = Octicons;
42 | break;
43 | case 'Zocial':
44 | this.Icon = Zocial;
45 | break;
46 | default:
47 | this.Icon = Ionicons;
48 | }
49 | }
50 |
51 | render() {
52 | const color = {color: this.props.color ? this.props.color : theme[this.theme].buttonColor};
53 | return(
54 |
59 | );
60 | }
61 |
62 | static propTypes = {
63 | family: PropTypes.string,
64 | name: PropTypes.string,
65 | color: PropTypes.string,
66 | theme: PropTypes.string,
67 | };
68 |
69 | static defaultProps = {
70 | size: size.iconSize
71 | };
72 | }
73 |
--------------------------------------------------------------------------------
/components/navbar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Image, Text, View, Platform, StatusBar } from 'react-native';
4 |
5 | import Icon from './icon';
6 | import Button from './button';
7 | import styles, { theme, size } from '../styles';
8 | import { isIOS, iOS, iconName, fixIconName } from '../utils';
9 |
10 | const BACK = 'back';
11 | const CLOSE = 'close';
12 | const LOGIN = 'login';
13 | const MENU = 'menu';
14 |
15 | const FADE = 'fade';
16 | const SLIDE = 'slide';
17 | const NONE = 'none';
18 |
19 | const DARK = 'dark';
20 | const LIGHT = 'light';
21 |
22 | const LEFT = 'left';
23 | const RIGHT = 'right';
24 |
25 | export default class Navbar extends Component {
26 |
27 | constructor(props) {
28 | super(props);
29 |
30 | this.hasLeftBtn = !!props.left;
31 | this.hasRightBtn = !!props.right;
32 | this.hasBothBtn = this.hasLeftBtn && this.hasRightBtn;
33 | this.iconPrefix = isIOS() ? 'ios' : 'md';
34 | this.theme = !!props.theme ? props.theme : isIOS() ? LIGHT : DARK;
35 | }
36 |
37 | _managePress(props) {
38 | switch (true) {
39 | case (props.disabled):
40 | return null;
41 | case (props.role == LOGIN):
42 | switch (true) {
43 | case (!!this.props.user):
44 | return props.onPress;
45 | default:
46 | return props.onPress;
47 | }
48 | case (props.role == MENU):
49 | return props.onPress;
50 | case (props.role == BACK):
51 | return props.onPress;
52 | case (props.role == CLOSE):
53 | return props.onPress;
54 | default:
55 | return props.onPress;
56 | }
57 | }
58 |
59 | _manageJustifyContentContainer() {
60 | switch (true) {
61 | case (this.hasBothBtn):
62 | return { justifyContent : 'space-between' };
63 | case (this.hasLeftBtn):
64 | return { justifyContent : 'flex-start' };
65 | case (this.hasRightBtn):
66 | return { justifyContent : 'flex-end' };
67 | }
68 | }
69 |
70 | renderStatusBar() {
71 |
72 | const customStatusBarBgColor = this.props.statusBar.bgColor ?
73 | { backgroundColor: this.props.statusBar.bgColor } : null;
74 |
75 | switch (true) {
76 | case (!!this.props.statusBar.hidden):
77 | return ;
78 | case (isIOS() && !this.props.statusBar.hidden):
79 | const statusBarStyle = { barStyle: (this.props.statusBar && this.props.statusBar.style) ?
80 | this.props.statusBar.style : theme[this.theme].statusBar.style }
81 | const iOsStatusBar = Object.assign({}, Navbar.defaultProps.statusBar.iOS, statusBarStyle);
82 |
83 | if (this.props.statusBar && this.props.statusBar.hidden) iOsStatusBar.hidden = this.props.statusBar.hidden;
84 | if (this.props.statusBar && this.props.statusBar.animation) iOsStatusBar.animated = this.props.statusBar.animation;
85 | if (this.props.statusBar && this.props.statusBar.transition) iOsStatusBar.showHideTransition = this.props.statusBar.transition;
86 |
87 | return
88 |
89 | ;
90 | case (!isIOS() && !this.props.statusBar.hidden):
91 | const bgStatusBarColor = this.props.statusBar.bgColor ?
92 | { backgroundColor: this.props.statusBar.bgColor } : theme[this.theme].statusBar;
93 | const androidStatusBar = Object.assign({}, Navbar.defaultProps.statusBar.android, bgStatusBarColor);
94 | return ;
95 | default:
96 | return ;
97 | }
98 | }
99 |
100 | _getImageTitleSource(prop) {
101 | switch (true) {
102 | case (prop.type == 'remote'):
103 | return {uri: prop.source};
104 | case (prop.type == 'local'):
105 | default:
106 | return prop.source;
107 | }
108 | }
109 |
110 | _getImageResizeMode(prop) {
111 | switch (true) {
112 | case (!!prop.resizeMode):
113 | return prop.resizeMode;
114 | case (prop.type == 'local'):
115 | return 'cover';
116 | case (prop.type == 'remote'):
117 | default:
118 | return 'contain';
119 | }
120 | }
121 |
122 | _getImageStyle(prop) {
123 | switch (true) {
124 | case (prop.type == 'remote'):
125 | return Object.assign({}, {
126 | width: size.screenWidth,
127 | height: size.navBarHeight
128 | }, prop.style);
129 | case (prop.type == 'local'):
130 | default:
131 | return Object.assign({}, prop.style);
132 | }
133 | }
134 |
135 | renderImage() {
136 |
137 | const image = ;
142 |
143 | switch (true) {
144 | case (isIOS()):
145 | return (
146 |
147 | {image}
148 |
149 | );
150 | default:
151 | return image;
152 | }
153 | }
154 |
155 | renderTitle() {
156 | const titleColor = {color: (this.props.titleColor) ? this.props.titleColor : theme[this.theme].titleColor};
157 | switch (true) {
158 | case (isIOS() && !!this.props.title && typeof this.props.title === "string"):
159 | return (
160 |
161 | {this.props.title}
162 |
163 | );
164 | case (!isIOS() && !!this.props.title && typeof this.props.title === "string"):
165 | return {this.props.title};
166 | case (!!this.props.title && typeof this.props.title !== "string"):
167 | return this.props.title;
168 | case (!!this.props.image):
169 | return this.renderImage();
170 | default:
171 | return null;
172 | }
173 | }
174 |
175 | renderIcon(props, labelPos, btnPos) {
176 | if ((!props.iconPos && labelPos == btnPos) || props.iconPos == labelPos) {
177 | const family = (props.iconFamily) ? props.iconFamily : 'Ionicons';
178 | switch (true) {
179 | case (props.role == MENU):
180 | const icon = (props.icon) ? fixIconName(props.icon) : iconName(this.iconPrefix, 'menu');
181 | return ;
182 | case (props.role == CLOSE):
183 | const iconClose = (props.icon) ? fixIconName(props.icon) : iconName(this.iconPrefix, 'close');
184 | return ;
185 | case (props.role == BACK):
186 | const iconBack = (props.icon) ? fixIconName(props.icon) : iconName(this.iconPrefix, 'arrow-back');
187 | return ;
188 | case (!!props.icon):
189 | return ;
190 | default:
191 | return null;
192 | }
193 | }
194 | return null;
195 | }
196 |
197 | renderLabel(props) {
198 | switch (true) {
199 | case (props.role == LOGIN):
200 | switch (true) {
201 | case (!!this.props.user):
202 | return props.logoutLabel || 'Logout';
203 | default:
204 | return props.loginLabel || 'Login';
205 | }
206 | case (props.role == BACK):
207 | switch (true) {
208 | case (isIOS() && !!props.label):
209 | return props.label;
210 | case (isIOS() && !!!props.label):
211 | return 'Back';
212 | case (!isIOS() && !!!props.label):
213 | default:
214 | return null;
215 | }
216 | case (!!props.label):
217 | return props.label;
218 | default:
219 | return null;
220 | }
221 | }
222 |
223 | renderBadge(props) {
224 | if (props.badge) {
225 | return (
226 |
229 |
232 | {props.badge}
233 |
234 |
235 | );
236 | }
237 | }
238 |
239 | renderButton(props, icon1_1, icon1_2, icon2_1, icon2_2, i = 0) {
240 | switch (true) {
241 | case (React.isValidElement(props)):
242 | return props;
243 | default:
244 | return (
245 |
262 | );
263 | }
264 | }
265 |
266 | renderLeftButton() {
267 | const left = this.props.left;
268 | switch (true) {
269 | case (isIOS() && Array.isArray(left)):
270 | return (
271 |
272 | {left.map((btn, i) => {
273 | return this.renderButton(btn, 'left', 'left', 'right', 'left', i);
274 | })}
275 |
276 | );
277 | case (isIOS() && typeof left === 'object'):
278 | return this.renderButton(left, 'left', 'left', 'right', 'left');
279 | case (!iOS() && Array.isArray(left)):
280 | return (
281 |
282 | {left.map((btn, i) => {
283 | return this.renderButton(btn, 'left', 'left', 'right', 'left', i);
284 | })}
285 | {this.renderTitle()}
286 |
287 | );
288 | case (!isIOS() && typeof left === 'object'):
289 | return (
290 |
291 | {this.renderButton(left, 'left', 'left', 'right', 'left')}
292 | {this.renderTitle()}
293 |
294 | );
295 | default:
296 | return null;
297 | }
298 | }
299 |
300 | renderRightButton() {
301 | const right = this.props.right;
302 | switch (true) {
303 | case (Array.isArray(right)):
304 | return (
305 |
306 | {right.map((btn, i) => {
307 | return this.renderButton(btn, 'left', 'right', 'right', 'right', i);
308 | })}
309 |
310 | );
311 | case (typeof right === 'object'):
312 | return this.renderButton(right, 'left', 'right', 'right', 'right');
313 | default:
314 | return null;
315 | }
316 | }
317 |
318 | renderBackgroundImage() {
319 | switch (true) {
320 | case (!!this.props.imageBackground && !!this.props.imageBackground.source):
321 | return (
322 |
327 | );
328 | default:
329 | return null;
330 | }
331 | }
332 |
333 | render() {
334 | const renderTitle = isIOS() ? this.renderTitle() : null;
335 | const bgColor = { backgroundColor: this.props.bgColor ? this.props.bgColor : theme[this.theme].bgNavbarColor };
336 | return (
337 |
338 | {this.renderStatusBar()}
339 | {this.renderBackgroundImage()}
340 |
341 | {renderTitle}
342 |
343 | {this.renderLeftButton()}
344 | {this.renderRightButton()}
345 |
346 |
347 |
348 | );
349 | }
350 |
351 | static statusBarShape = {
352 | style: PropTypes.oneOf(['light-content', 'default', ]),
353 | hidden: PropTypes.bool,
354 | bgColor: PropTypes.string,
355 | hideAnimation: PropTypes.oneOf([FADE, SLIDE, NONE, ]),
356 | showAnimation: PropTypes.oneOf([FADE, SLIDE, NONE, ]),
357 | };
358 |
359 | static badgeShape = {
360 | value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
361 | bgColor: PropTypes.string,
362 | textColor: PropTypes.string,
363 | position: PropTypes.oneOf([LEFT, RIGHT, ]),
364 | };
365 |
366 | static buttonPropTypes = {
367 | icon: PropTypes.string,
368 | iconFamily: PropTypes.string,
369 | iconPos: PropTypes.string,
370 | iconSize: PropTypes.number,
371 | iconColor: PropTypes.string,
372 | label: PropTypes.string,
373 | badge: PropTypes.oneOfType([
374 | PropTypes.number,
375 | PropTypes.string,
376 | PropTypes.shape(this.badgeShape)
377 | ]),
378 | onPress: PropTypes.func,
379 | disabled: PropTypes.bool,
380 | role: PropTypes.oneOf([BACK, CLOSE, LOGIN, MENU]),
381 | style: PropTypes.object,
382 | };
383 |
384 | static imagePropTypes = {
385 | source: PropTypes.oneOfType([
386 | PropTypes.string,
387 | PropTypes.number,
388 | ]),
389 | type: PropTypes.oneOf(['local', 'remote']),
390 | resizeMode: PropTypes.oneOf(['cover', 'contain', 'stretch', 'repeat', 'center']),
391 | style: PropTypes.object
392 | };
393 |
394 | static defaultProps = {
395 | statusBar: {
396 | iOS: {
397 | animated: true,
398 | hidden: false,
399 | showHideTransition: FADE,
400 | networkActivityIndicatorVisible: false,
401 | },
402 | android: {
403 | animated: true,
404 | hidden: false,
405 | translucent: false,
406 | }
407 | }
408 | };
409 |
410 | static FADE = FADE;
411 | static SLIDE = SLIDE;
412 | static NONE = NONE;
413 | static LEFT = LEFT;
414 | static RIGHT = RIGHT;
415 | }
416 |
417 | const buttonShape = PropTypes.shape(Navbar.buttonPropTypes);
418 |
419 | Navbar.propTypes = {
420 | theme: PropTypes.oneOf([DARK, LIGHT]),
421 | title: PropTypes.oneOfType([
422 | PropTypes.func,
423 | PropTypes.string,
424 | PropTypes.element
425 | ]),
426 | titleColor: PropTypes.string,
427 | bgColor: PropTypes.string,
428 | image: PropTypes.shape(Navbar.imagePropTypes),
429 | imageBackground: PropTypes.shape(Navbar.imagePropTypes),
430 | statusBar: PropTypes.shape(Navbar.statusBarShape),
431 | left: PropTypes.oneOfType([
432 | buttonShape,
433 | PropTypes.arrayOf(buttonShape),
434 | PropTypes.element,
435 | PropTypes.func,
436 | ]),
437 | right: PropTypes.oneOfType([
438 | buttonShape,
439 | PropTypes.arrayOf(buttonShape),
440 | PropTypes.element,
441 | PropTypes.func,
442 | ]),
443 | style: PropTypes.object,
444 | user: PropTypes.oneOfType([
445 | PropTypes.object,
446 | PropTypes.bool
447 | ])
448 | };
449 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import Container from './components/container'
2 | import Navbar from './components/navbar';
3 | import Icon from './components/icon';
4 | import { iOS, isIOS } from './utils';
5 |
6 | module.exports = {
7 | Container: Container,
8 | Navbar: Navbar,
9 | Icon: Icon,
10 | iOS: iOS,
11 | isIOS: isIOS,
12 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "navbar-native",
3 | "version": "1.6.2",
4 | "description": "a fully customizable navbar component for React-Native that works for both iOS and Android",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "test"
8 | },
9 | "repository": {
10 | "type" : "git",
11 | "url" : "https://github.com/redbaron76/navbar-native.git"
12 | },
13 | "keywords": [
14 | "react",
15 | "native",
16 | "navbar"
17 | ],
18 | "dependencies": {
19 | "prop-types": "^15.6.2",
20 | "react-native-keyboard-aware-scroll-view": "^0.6.0",
21 | "react-native-vector-icons": "~4.6.0",
22 | "react-native-spinkit": "~1.1.1"
23 | },
24 | "author": "Fabio Fumis (https://github.com/redbaron76)",
25 | "license": "MIT"
26 | }
27 |
--------------------------------------------------------------------------------
/styles.js:
--------------------------------------------------------------------------------
1 | import { color, size, font, theme,iOS } from './utils';
2 |
3 | export default navbarStyles = {
4 | mainContainer: {
5 | flex: 1,
6 | position: 'relative',
7 | },
8 | contentContainer: {
9 | backgroundColor: color.bgContentColor,
10 | },
11 | contentAbsolute: {
12 | position: 'absolute',
13 | top: 0,
14 | right: 0,
15 | bottom: 0,
16 | left: 0,
17 | },
18 | navBarContainer: {
19 | position: 'relative',
20 | zIndex: 1,
21 | },
22 | statusBar: {
23 | height: size.statusBarHeight,
24 | },
25 | navBar: {
26 | height: size.navBarHeight,
27 | position: 'relative',
28 | flexDirection: 'row',
29 | },
30 | customTitle: {
31 | position: 'absolute',
32 | left: 0,
33 | right: 0,
34 | bottom: 7,
35 | alignItems: 'center',
36 | },
37 | navBarButtonContainer: {
38 | position: 'absolute',
39 | left: size.navBarButtonContainer.left,
40 | right: size.navBarButtonContainer.right,
41 | top: 0,
42 | bottom: 0,
43 | flexDirection: 'row',
44 | alignItems: 'stretch',
45 | },
46 | navBarMultiButtonContainer: {
47 | flexDirection: 'row',
48 | justifyContent: 'space-between',
49 | alignItems: 'center',
50 | },
51 | navBarButtonWrapper: {
52 | flexDirection: 'row',
53 | justifyContent: 'space-between',
54 | alignItems: 'center',
55 | },
56 | navBarButton: {
57 | position: 'relative',
58 | flexDirection: 'row',
59 | alignItems: 'stretch',
60 | },
61 | navBarButtonDisabled: {
62 | opacity: .8
63 | },
64 | navBarButtonText: {
65 | fontSize: 17,
66 | fontFamily: font.buttonText,
67 | marginBottom: size.navBarButtonText.marginBottom,
68 | backgroundColor: 'transparent',
69 | },
70 | navBarTitleContainer: {
71 | position: 'absolute',
72 | left: 0,
73 | right: 0,
74 | top: 0,
75 | bottom: 0,
76 | justifyContent: 'center',
77 | alignItems: 'center',
78 | },
79 | navBarTitleText: {
80 | fontSize: 17,
81 | fontFamily: font.titleText,
82 | color: color.titleColor,
83 | fontWeight: size.navBarTitleText.fontWeight,
84 | marginLeft: size.navBarTitleText.marginLeft,
85 | backgroundColor: 'transparent',
86 | },
87 | listRowDefault: {
88 | row: {
89 | flexDirection:'row',
90 | alignItems: 'center',
91 | justifyContent:'flex-start',
92 | paddingTop: 18,
93 | paddingBottom: 18,
94 | paddingLeft: 8,
95 | paddingRight: 8,
96 | borderBottomWidth: 1,
97 | borderBottomColor: color.borderColor
98 | },
99 | text: {
100 | flex: 1,
101 | fontSize: 17
102 | }
103 | },
104 | iconStyle: {
105 | marginTop: size.iconStyle.marginTop,
106 | backgroundColor: 'transparent',
107 | },
108 | imageBackground: {
109 | position: 'absolute',
110 | top: 0,
111 | right: 0,
112 | bottom: 0,
113 | left: 0,
114 | },
115 | loadingContainer: {
116 | view: {
117 | flex: 1,
118 | position: 'absolute',
119 | top: 0,
120 | right: 0,
121 | bottom: 0,
122 | left: 0,
123 | zIndex: 2,
124 | flexDirection:'column',
125 | alignItems: 'center',
126 | justifyContent: 'center',
127 | },
128 | spinner: {
129 | alignItems: 'center',
130 | },
131 | spinnerNotCover: {
132 | marginTop: iOS(size.navBarHeight + size.statusBarHeight, size.navBarHeight),
133 | },
134 | text: {
135 | marginTop: 10,
136 | color: color.white,
137 | fontSize: 20,
138 | }
139 | },
140 | buttonBadgeContainer: {
141 | position: 'absolute',
142 | top: 5,
143 |
144 | flexDirection: 'column',
145 | alignItems: 'center',
146 | justifyContent: 'center',
147 | paddingHorizontal: 2,
148 | borderRadius: 10,
149 | height: 20,
150 | width: 20,
151 | },
152 | buttonBadge: {
153 | fontSize: 13,
154 | textAlign: 'center',
155 | backgroundColor: 'transparent'
156 | },
157 | };
158 |
159 | export { theme, size };
--------------------------------------------------------------------------------
/utils.js:
--------------------------------------------------------------------------------
1 | import { Platform, Dimensions } from 'react-native';
2 |
3 | export function iOS(a, b) {
4 | return Platform.OS == 'ios' ? a : b;
5 | }
6 |
7 | export function isIOS() {
8 | return Platform.OS == 'ios';
9 | }
10 |
11 | export function iconName(prefix, name) {
12 | return `${prefix}-${name}`;
13 | }
14 |
15 | export function fixIconName(icon) {
16 | switch (true) {
17 | case (isIOS() && icon.startsWith('md-')):
18 | return icon.replace('md-', 'ios-');
19 | case (!isIOS() && icon.startsWith('ios-')):
20 | return icon.replace('ios-', 'md-');
21 | default:
22 | return icon;
23 | }
24 | }
25 |
26 | export const size = {
27 | iconSize: iOS(30, 28),
28 | iconStyle: {
29 | marginTop: iOS(4, 0),
30 | },
31 |
32 | navBarButtonContainer: {
33 | left: iOS(8, 12),
34 | right: iOS(8, 12),
35 | },
36 |
37 | navBarButtonText: {
38 | marginBottom: iOS(0, 0),
39 | },
40 |
41 | navBarTitleText: {
42 | marginLeft: iOS(0, 16),
43 | fontWeight: iOS('500', '400'),
44 | },
45 |
46 | navBarHeight: 44,
47 | statusBarHeight: 20,
48 |
49 | screenWidth: Dimensions.get('window').width,
50 | screenHeight: Dimensions.get('window').height,
51 | };
52 |
53 | export const font = {
54 | buttonText: iOS('HelveticaNeue-Medium', 'Roboto'),
55 | titleText: iOS('HelveticaNeue-Medium', 'Roboto'),
56 | };
57 |
58 | export const color = {
59 | bgNavbarColor: iOS('#f2f2f2', '#212121'),
60 | bgContentColor: iOS('#000000', '#303030'),
61 | bgLoadingColor: iOS('rgba(0,0,0,.8)','rgba(0,0,0,.8)'),
62 | buttonColor: iOS('#387afe', '#ffffff'),
63 | titleColor: iOS('#000000', '#ffffff'),
64 | borderColor: iOS('#ceced2', '#757575'),
65 | white: '#ffffff',
66 | };
67 |
68 | export const theme = {
69 | light: {
70 | bgNavbarColor: iOS('#f2f2f2', '#f5f5f5'),
71 | buttonColor: iOS('#387afe', '#707070'),
72 | titleColor: iOS('#000000', '#000000'),
73 | badgeBgColor: iOS('#2b2b2b', '#212121'),
74 | badgeTextColor: iOS('#f2f2f2', '#f5f5f5'),
75 | statusBar: iOS({
76 | style: 'default'
77 | }, {
78 | backgroundColor: '#707070',
79 | })
80 | },
81 | dark: {
82 | bgNavbarColor: iOS('#2b2b2b', '#212121'),
83 | buttonColor: iOS('#ffffff', '#ffffff'),
84 | titleColor: iOS('#ffffff', '#ffffff'),
85 | badgeBgColor: iOS('#f2f2f2', '#f5f5f5'),
86 | badgeTextColor: iOS('#2b2b2b', '#212121'),
87 | statusBar: iOS({
88 | style: 'light-content'
89 | }, {
90 | backgroundColor: '#000000',
91 | })
92 | }
93 | };
--------------------------------------------------------------------------------