├── index.js ├── img ├── 1.png ├── 2.png ├── 3.png └── 4.png ├── .gitignore ├── example ├── demo2.js └── demo1.js ├── LICENSE ├── README.md └── tab.js /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./tab'); -------------------------------------------------------------------------------- /img/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vczero/react-native-tab-menu/HEAD/img/1.png -------------------------------------------------------------------------------- /img/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vczero/react-native-tab-menu/HEAD/img/2.png -------------------------------------------------------------------------------- /img/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vczero/react-native-tab-menu/HEAD/img/3.png -------------------------------------------------------------------------------- /img/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vczero/react-native-tab-menu/HEAD/img/4.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | 7 | # Runtime data 8 | pids 9 | *.pid 10 | *.seed 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 19 | .grunt 20 | 21 | # node-waf configuration 22 | .lock-wscript 23 | 24 | # Compiled binary addons (http://nodejs.org/api/addons.html) 25 | build/Release 26 | 27 | # Dependency directory 28 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 29 | node_modules 30 | 31 | 32 | .idea -------------------------------------------------------------------------------- /example/demo2.js: -------------------------------------------------------------------------------- 1 | var React = require('react-native'); 2 | var MenuList = require('./../tab'); 3 | 4 | var data = { 5 | "Language": { 6 | "All": ["All"], 7 | "Web Front End": [ 8 | "HTML", 9 | "CSS", 10 | "JavaScript" 11 | ], 12 | "Server": [ 13 | "Node.js", 14 | "PHP", 15 | "Python", 16 | "Ruby" 17 | ] 18 | }, 19 | "Tool":{ 20 | "All": ["All"], 21 | "Apple": ["Xcode"], 22 | "Other": ["Sublime Text", "WebStrom",] 23 | } 24 | }; 25 | 26 | 27 | var App = React.createClass({ 28 | render: function(){ 29 | return ( 30 | 31 | 32 | 33 | ); 34 | }, 35 | onPress: function(val){ 36 | alert(val); 37 | } 38 | }); 39 | 40 | 41 | AppRegistry.registerComponent('app', () => App); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 vczero 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /example/demo1.js: -------------------------------------------------------------------------------- 1 | var React = require('react-native'); 2 | var MenuList = require('./../tab'); 3 | 4 | var { 5 | AppRegistry, 6 | StyleSheet, 7 | Text, 8 | View, 9 | ScrollView, 10 | TouchableOpacity, 11 | } = React; 12 | 13 | var data = { 14 | "全部区域": { 15 | "全部区域": ["全部区域"], 16 | "热门商圈": [ 17 | "虹桥地区", 18 | "徐家汇地区", 19 | "淮海路商业区", 20 | "静安寺地区", 21 | "上海火车站地区", 22 | "浦东陆家嘴金融贸易区", 23 | "四川北路商业区", 24 | "人民广场地区", 25 | "南翔、安亭汽车城" 26 | ], 27 | "热门行政区": [ 28 | "静安区", 29 | "徐汇区", 30 | "长宁区", 31 | "黄埔区", 32 | "虹口区", 33 | "宝山区", 34 | "闸北区" 35 | ] 36 | }, 37 | "地铁沿线":{ 38 | "地铁全线": ["地铁全线"], 39 | "一号线": ["莘庄站", "外环路站", "莲花路站", "锦江乐园站", "上海南站站", "漕宝路站"], 40 | "二号线": ["浦东国际机场站", "海天三路站", "远东大道站", "凌空路站"] 41 | } 42 | }; 43 | 44 | 45 | var App = React.createClass({ 46 | render: function(){ 47 | return ( 48 | 49 | 50 | 51 | ); 52 | }, 53 | onPress: function(val){ 54 | alert(val); 55 | } 56 | }); 57 | 58 | 59 | AppRegistry.registerComponent('app', () => App); 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-tab 2 | react-native-tab is a simple module for add a "Tab Menu" to your React Native app. 3 | 4 | ### Features 5 | ![](img/1.png) 6 | ![](img/3.png) 7 | 8 | ### Usage 9 | 10 | npm install react-native-tab 11 | 12 | ![](img/4.png) 13 | 14 | 15 | ##### Demo1: 16 | 17 | var React = require('react-native'); 18 | var MenuList = require('react-native-tab'); 19 | var { 20 | AppRegistry, 21 | StyleSheet, 22 | Text, 23 | View, 24 | ScrollView, 25 | TouchableOpacity, 26 | } = React; 27 | 28 | var data = { 29 | "Language": { 30 | "All": ["All"], 31 | "Web Front End": [ 32 | "HTML", 33 | "CSS", 34 | "JavaScript" 35 | ], 36 | "Server": [ 37 | "Node.js", 38 | "PHP", 39 | "Python", 40 | "Ruby" 41 | ] 42 | }, 43 | "Tool":{ 44 | "All": ["All"], 45 | "Apple": ["Xcode"], 46 | "Other": ["Sublime Text", "WebStrom",] 47 | } 48 | }; 49 | 50 | 51 | var App = React.createClass({ 52 | render: function(){ 53 | return ( 54 | 55 | 56 | 57 | ); 58 | }, 59 | onPress: function(val){ 60 | alert(val); 61 | } 62 | }); 63 | 64 | 65 | AppRegistry.registerComponent('app', () => App); 66 | 67 | 68 | ##### Demo2: 69 | 70 | var React = require('react-native'); 71 | var MenuList = require('react-native-tab'); 72 | var { 73 | AppRegistry, 74 | StyleSheet, 75 | Text, 76 | View, 77 | ScrollView, 78 | TouchableOpacity, 79 | } = React; 80 | 81 | var data = { 82 | "全部区域": { 83 | "全部区域": ["全部区域"], 84 | "热门商圈": [ 85 | "虹桥地区", 86 | "徐家汇地区", 87 | "淮海路商业区", 88 | "静安寺地区", 89 | "上海火车站地区", 90 | "浦东陆家嘴金融贸易区", 91 | "四川北路商业区", 92 | "人民广场地区", 93 | "南翔、安亭汽车城" 94 | ], 95 | "热门行政区": [ 96 | "静安区", 97 | "徐汇区", 98 | "长宁区", 99 | "黄埔区", 100 | "虹口区", 101 | "宝山区", 102 | "闸北区" 103 | ] 104 | }, 105 | "地铁沿线":{ 106 | "地铁全线": ["地铁全线"], 107 | "一号线": ["莘庄站", "外环路站", "莲花路站", "锦江乐园站", "上海南站站", "漕宝路站"], 108 | "二号线": ["浦东国际机场站", "海天三路站", "远东大道站", "凌空路站"] 109 | } 110 | }; 111 | 112 | 113 | var App = React.createClass({ 114 | render: function(){ 115 | return ( 116 | 117 | 118 | 119 | ); 120 | }, 121 | onPress: function(val){ 122 | alert(val); 123 | } 124 | }); 125 | 126 | 127 | AppRegistry.registerComponent('app', () => App); 128 | 129 | ### Properties 130 | 131 | + data: 你需要渲染的数据,格式参见demo。 132 | + tabSelected: 默认选中第几个tab,number类型。 133 | + nSelected: 默认选中tab下的二级菜单项,number类型。 -------------------------------------------------------------------------------- /tab.js: -------------------------------------------------------------------------------- 1 | var React = require('react-native'); 2 | var { 3 | StyleSheet, 4 | Text, 5 | View, 6 | ScrollView, 7 | TouchableOpacity, 8 | } = React; 9 | 10 | 11 | //设定内置的属性 12 | //选中项,例如:_type_0_2 表示第一个Tab选中,并且第二个Tab中的第三项选中 13 | var prefixType = '_type_'; 14 | 15 | //选中项样式,例如:_style_0_2 表示第一个Tab选中,并且第二个Tab中的第三项选中时的样式 16 | var prefixStyle = '_style_'; 17 | 18 | //默认左侧选中的背景颜色 19 | var defaultBackgroundColor = {backgroundColor:'#fff'}; 20 | 21 | var MenuList = React.createClass({ 22 | getInitialState: function(){ 23 | var data = this.props.data; 24 | //左侧选择的index 25 | var nSelected = this.props.nSelected; 26 | //头部选择的index 27 | var tabSelected = this.props.tabSelected; 28 | var obj = {}; 29 | var kIndex = 0; 30 | for(var k in data){ 31 | var childData = data[k]; 32 | var cIndex = 0; 33 | for(var c in childData){ 34 | var type = prefixType + k + '_' + c; 35 | var style = prefixStyle + k + '_' + c; 36 | obj[type] = false; 37 | obj[style] = {}; 38 | //设定默认选中项 39 | if(nSelected === cIndex && tabSelected === kIndex){ 40 | obj[type] = true; 41 | obj[style] = defaultBackgroundColor; 42 | } 43 | cIndex++; 44 | } 45 | kIndex++; 46 | } 47 | obj.tabSelected = tabSelected; 48 | obj.nSelected = nSelected; 49 | return obj; 50 | }, 51 | render: function(){ 52 | var header = this.renderlHeader(); 53 | var left = this.renderLeft(); 54 | var right = this.renderRight(); 55 | return ( 56 | 57 | 58 | {header} 59 | 60 | 61 | 62 | {left} 63 | 64 | 65 | {right} 66 | 67 | 68 | 69 | 70 | ); 71 | }, 72 | 73 | //渲染头部TabBar 74 | renderlHeader: function(){ 75 | var data = this.props.data; 76 | var tabSelected = this.state.tabSelected; 77 | var header = []; 78 | var tabIndex = 0; 79 | for(var i in data){ 80 | var tabStyle = null; 81 | if(tabIndex === tabSelected){ 82 | tabStyle=[styles.header_text, styles.active_blue]; 83 | }else{ 84 | tabStyle = [styles.header_text]; 85 | } 86 | header.push( 87 | 89 | {i} 90 | 91 | ); 92 | tabIndex ++; 93 | } 94 | return header; 95 | }, 96 | 97 | //渲染左侧 98 | renderLeft: function(){ 99 | var data = this.props.data; 100 | var tabSelected = this.state.tabSelected; 101 | var leftPannel = []; 102 | var index = 0; 103 | for(var i in data){ 104 | if(index === tabSelected){ 105 | for(var k in data[i]){ 106 | var style = this.state[prefixStyle + i + '_' + k]; 107 | leftPannel.push( 108 | {k}); 110 | } 111 | break; 112 | } 113 | index ++; 114 | } 115 | return leftPannel; 116 | }, 117 | //渲染右边,二级菜单 118 | renderRight: function(){ 119 | var data = this.props.data; 120 | var tabSelected = this.state.tabSelected; 121 | var nSelected = this.state.nSelected; 122 | var index = 0; 123 | var rightPannel = []; 124 | for(var i in data){ 125 | if(tabSelected === index ){ 126 | for(var k in data[i]){ 127 | if(this.state[prefixType + i + '_' + k]){ 128 | for(var j in data[i][k]){ 129 | rightPannel.push( 130 | {data[i][k][j]}); 131 | } 132 | break; 133 | } 134 | } 135 | } 136 | index ++; 137 | } 138 | return rightPannel; 139 | }, 140 | //点击左侧,展示右侧二级菜单 141 | leftPress: function(tabIndex, nIndex){ 142 | var obj = {}; 143 | for(var k in this.state){ 144 | //将prefixType或者prefixStyle类型全部置false 145 | if(k.indexOf(prefixType) > -1){ 146 | var obj = {}; 147 | obj[k] = false; 148 | this.setState(obj); 149 | } 150 | if(k.indexOf(prefixStyle) > -1){ 151 | var obj = {}; 152 | obj[k] = {}; 153 | this.setState(obj); 154 | } 155 | } 156 | obj[prefixType + tabIndex + '_' + nIndex] = true; 157 | obj[prefixStyle + tabIndex + '_' + nIndex] = defaultBackgroundColor; 158 | this.setState(obj); 159 | }, 160 | //头部点击事件即Tab切换事件 161 | headerPress: function(title){ 162 | var data = this.props.data; 163 | var index = 0; 164 | for(var i in data){ 165 | if(i === title){ 166 | this.setState({ 167 | tabSelected: index, 168 | }); 169 | var obj = {}; 170 | var n = 0; 171 | for(var k in data[i]){ 172 | if(n !== 0){ 173 | obj[prefixType + i + '_' + k] = false; 174 | obj[prefixStyle + i + '_' + k] = {}; 175 | }else{ 176 | obj[prefixType + i + '_' + k] = true; 177 | obj[prefixStyle + i + '_' + k] = defaultBackgroundColor; 178 | } 179 | n ++; 180 | } 181 | this.setState(obj); 182 | } 183 | index ++; 184 | } 185 | } 186 | }); 187 | 188 | var styles = StyleSheet.create({ 189 | container:{ 190 | height:240, 191 | flex:1, 192 | borderTopWidth:1, 193 | borderBottomWidth:1, 194 | borderColor:'#ddd' 195 | }, 196 | row:{ 197 | flexDirection: 'row' 198 | }, 199 | flex_1:{ 200 | flex:1 201 | }, 202 | header:{ 203 | height:35, 204 | borderBottomWidth:1, 205 | borderColor:'#DFDFDF', 206 | backgroundColor:'#F5F5F5' 207 | }, 208 | header_text:{ 209 | color:'#7B7B7B', 210 | fontSize:15 211 | }, 212 | center:{ 213 | justifyContent:'center', 214 | alignItems:'center' 215 | }, 216 | left_pannel:{ 217 | backgroundColor:'#F2F2F2', 218 | }, 219 | left_row:{ 220 | height:30, 221 | lineHeight:20, 222 | fontSize:14, 223 | color:'#7C7C7C', 224 | }, 225 | right_pannel:{ 226 | marginLeft:10 227 | }, 228 | active_blue:{ 229 | color: '#00B7EB' 230 | }, 231 | active_fff:{ 232 | backgroundColor:'#fff' 233 | } 234 | }); 235 | 236 | module.exports = MenuList; 237 | 238 | 239 | 240 | 241 | --------------------------------------------------------------------------------