├── .babelrc ├── .eslintignore ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── __setup__ └── enzyme.js ├── __tests__ ├── .eslintrc ├── TabBarVertical.test.js └── __snapshots__ │ └── TabBarVertical.test.js.snap ├── example ├── .babelrc ├── .buckconfig ├── .eslintrc ├── .expo │ ├── packager-info.json │ └── settings.json ├── .watchmanconfig ├── App.js ├── README.md ├── app.json ├── assets │ ├── VerticalTabView.gif │ ├── album-art-1.jpg │ ├── album-art-2.jpg │ ├── album-art-3.jpg │ ├── album-art-4.jpg │ ├── album-art-5.jpg │ ├── album-art-6.jpg │ ├── album-art-7.jpg │ ├── album-art-8.jpg │ ├── avatar-1.png │ ├── avatar-2.png │ ├── book.jpg │ └── icon.png ├── package.json ├── rn-cli.config.js ├── src │ ├── App.js │ ├── BottomBarIconTextExample.js │ ├── CoverflowExample.js │ ├── NativeDriverExample.js │ ├── NoAnimationExample.js │ ├── TopBarIconExample.js │ ├── TopBarTextExample.js │ ├── VerticalTabBarTextExample.js │ └── shared │ │ ├── Albums.js │ │ ├── Article.js │ │ ├── Chat.js │ │ ├── Contacts.js │ │ └── Profile.js └── yarn.lock ├── flow-typed └── npm │ ├── @expo │ └── vector-icons_vx.x.x.js │ ├── enzyme_v3.x.x.js │ ├── flow-bin_v0.x.x.js │ ├── jest_v21.x.x.js │ └── prop-types_v15.x.x.js ├── package.json ├── src ├── TabBarVertical.js ├── TabViewVertical.js ├── TouchableItem.js └── index.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | flow-typed/ 3 | example/ 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-satya164", 3 | 4 | "plugins": ["react-native-globals"], 5 | 6 | "env": { 7 | "es6": true, 8 | "react-native-globals/all": true, 9 | }, 10 | 11 | "rules": { 12 | "eslint-comments/no-unused-disable": "off" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore templates for 'react-native init' 6 | .*/local-cli/templates/.* 7 | 8 | ; Ignore the Dangerfile 9 | /bots/dangerfile.js 10 | 11 | ; Ignore "BUCK" generated dirs 12 | /\.buckd/ 13 | 14 | ; Ignore unexpected extra "@providesModule" 15 | .*/node_modules/.*/node_modules/fbjs/.* 16 | 17 | ; Ignore duplicate module providers 18 | ; For RN Apps installed via npm, "Libraries" folder is inside 19 | ; "node_modules/react-native" but in the source repo it is in the root 20 | .*/Libraries/react-native/React.js 21 | 22 | ; Ignore polyfills 23 | .*/Libraries/polyfills/.* 24 | 25 | ; Ignore metro 26 | .*/node_modules/metro/.* 27 | 28 | ; Ignore duplicate modules under example/ 29 | .*/example/node_modules/fbjs/.* 30 | .*/example/node_modules/fbemitter/.* 31 | .*/example/node_modules/react/.* 32 | .*/example/node_modules/react-native/.* 33 | .*/example/\.buckd/ 34 | 35 | ; Ignore duplicate modules under docs/ 36 | .*/docs/node_modules/fbjs/.* 37 | .*/docs/node_modules/react/.* 38 | 39 | ; Ignore some modules we don't need to parse 40 | .*/node_modules/prettier/.* 41 | .*/node_modules/eslint.* 42 | 43 | [untyped] 44 | .*/node_modules/expo/.* 45 | .*/node_modules/xdl/.* 46 | .*/node_modules/react-native-gesture-handler/.* 47 | 48 | [include] 49 | 50 | [libs] 51 | node_modules/react-native/Libraries/react-native/react-native-interface.js 52 | node_modules/react-native/flow-github 53 | node_modules/react-native/flow 54 | 55 | [options] 56 | emoji=true 57 | 58 | module.system=haste 59 | 60 | munge_underscores=true 61 | 62 | module.name_mapper='^expo$' -> 'emptyObject' 63 | module.name_mapper='^react-native-gesture-handler$' -> 'emptyObject' 64 | 65 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 66 | 67 | suppress_type=$FlowIssue 68 | suppress_type=$FlowFixMe 69 | suppress_type=$FlowFixMeProps 70 | suppress_type=$FlowFixMeState 71 | 72 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*[react_native_oss|react_native_fb][a-z,_]*\\)?)\\) 73 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*[react_native_oss|react_native_fb][a-z,_]*\\)?)\\)?:? #[0-9]+ 74 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 75 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 76 | 77 | [version] 78 | ^0.67.1 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | example/node_modules/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # next.js build output 62 | .next 63 | 64 | # ide files 65 | .idea 66 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | tabWidth: 2 3 | trailingComma: "es5" 4 | useTabs: false 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Knight Point Systems, LLC / Michael S. Kazmier 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-vertical-tab-view 2 | 3 | An extension to [react-native-tab-view](https://github.com/react-native-community/react-native-tab-view) which provides 4 | a vertical tab bar (great for landscape and tablet layouts). This work is largely derived from the excellent work that 5 | [@satya164](https://github.com/satya164) has done on react-native-tab-view, including many of his suggestions for extracting 6 | this into a stand alone library. 7 | 8 | This library is API compatible with react-native-tab-view and can be used as a drop in replacement as it re-exports all 9 | of the named exports from react-native-tab-view. 10 | 11 | ![Vertical Tab Example](https://github.com/DaKaZ/react-native-vertical-tab-view/blob/master/example/assets/VerticalTabView.gif?raw=true) 12 | 13 | 14 | ## Installation ## 15 | 16 | ``` 17 | yarn add react-native-vertical-tab-view 18 | ``` 19 | 20 | ## Quick Start ## 21 | 22 | See the docs at [react-native-tab-view](https://github.com/react-native-community/react-native-tab-view#quick-start) 23 | 24 | A quick overview, first you need the TabView setup: 25 | 26 | ```javascript 27 | 35 | ``` 36 | 37 | and then you need the TabBar: 38 | 39 | ```javascript 40 | _renderTabs = (landscape: boolean, otherProps: SceneRendererProps): Element<*> => { 41 | const SelectedTabBar = landscape ? TabBarVertical : TabBar; 42 | return ( 43 | 52 | ); 53 | }; 54 | ``` 55 | 56 | ### Tab Style ### 57 | 58 | This is IMPORTANT: make sure you pass in HEIGHT with your tab style: 59 | 60 | ```javascript 61 | const styles = StyleSheet.create({ 62 | container: { 63 | flex: 1, 64 | backgroundColor: '#EDECED' 65 | }, 66 | tabbar: { 67 | backgroundColor: '#205493' 68 | }, 69 | tab: { 70 | width: 110, 71 | height: 80 72 | }, 73 | icon: { 74 | backgroundColor: 'transparent' 75 | color: '#ffffff' 76 | }, 77 | indicator: { 78 | width: 110, 79 | height: 80, 80 | backgroundColor: '#F6F7F8' 81 | }, 82 | label: { 83 | textAlign: 'center', 84 | fontSize: 12, 85 | fontFamily: 'Source Sans Pro', 86 | paddingTop: 5, 87 | color: '#F6F7F8', 88 | backgroundColor: 'transparent' 89 | } 90 | }); 91 | ``` 92 | 93 | 94 | ## Using with Orientation ## 95 | 96 | One of the best use cases for the vertical tabs is when the device is used in landscape mode, you shift 97 | the tabs from the bottom to the left. Check out [react-native-orientation](https://github.com/yamill/react-native-orientation) 98 | for help getting the orientation setup, then look at an implementation like this: 99 | 100 | ```javascript 101 | class TabsScreen extends PureComponent { 102 | static defaultProps = { 103 | orientation: 'PORTRAIT' 104 | }; 105 | 106 | constructor(props: PropsType) { 107 | super(props); 108 | this.state = { 109 | index: 0, 110 | routes: [ 111 | { key: '1', title: 'DASHBOARD', icon: 'tachometer', path: 'dashboard' }, 112 | { key: '2', title: 'EMERGENCY', icon: 'phone', path: 'emergency' }, 113 | { key: '3', title: 'FINANCE', icon: 'pie-chart', path: 'finance' }, 114 | { key: '4', title: 'PERFORMANCE', icon: 'line-chart', path: 'performance' }, 115 | { key: '5', title: 'FACILITIES', icon: 'building', path: 'facilities' }, 116 | { key: '6', title: 'HUMAN RESOURCES', icon: 'users', path: 'human_resources' } 117 | ] 118 | }; 119 | } 120 | 121 | _handleIndexChange = (index: number) => { 122 | this.setState({ index }); 123 | }; 124 | 125 | _renderLabelFactory = (props: TabScreenSceneRenderPropsType): TabBarCallbackType => ( 126 | ({ route }: TabScreenSceneType): Element<*> => { 127 | const index = props.navigationState.routes.indexOf(route); 128 | const inputRange = 129 | props.navigationState.routes.map((x: TabScreenRouteType, i: number): number => i); 130 | const outputRange = inputRange.map((inputIndex: number): string => 131 | (inputIndex === index ? Colors.lightBlue : Colors.dhsWhite)); 132 | const color = props.position.interpolate({ 133 | inputRange, 134 | outputRange 135 | }); 136 | return {route.title}; 137 | } 138 | ); 139 | 140 | _renderIconFactory = (props: TabScreenSceneRenderPropsType): TabBarCallbackType => ( 141 | ({ route }: TabScreenSceneType): Element<*> => { 142 | const index = props.navigationState.routes.indexOf(route); 143 | const inputRange = 144 | props.navigationState.routes.map((x: TabScreenRouteType, i: number): number => i); 145 | const outputRange = inputRange.map((inputIndex: number): string => 146 | (inputIndex === index ? Colors.lightBlue : Colors.dhsWhite)); 147 | const color = props.position.interpolate({ 148 | inputRange, 149 | outputRange 150 | }); 151 | const AnimatedIcon = Animated.createAnimatedComponent(Icon); 152 | return ; 153 | } 154 | ); 155 | 156 | _renderTabs = (landscape: boolean, otherProps: SceneRendererProps): Element<*> => { 157 | const SelectedTabBar = landscape ? TabBarVertical : TabBar; 158 | return ( 159 | 168 | ); 169 | }; 170 | 171 | _renderScene = SceneMap({ 172 | '1': Screen1, 173 | '2': Screen2, 174 | '3': Screen3, 175 | '4': Screen4, 176 | '5': Screen5, 177 | '6': Screen6 178 | }); 179 | 180 | render(): Element<*> { 181 | // Orientation coming in from react-native-orientation 182 | const landscape = this.props.orientation.split('-')[0] === 'LANDSCAPE'; 183 | const SelectedTabView = landscape ? TabViewVertical : TabView; 184 | const initialLayout = { width: 600, height: 400 }; 185 | return ( 186 | this._renderTabs(landscape, props)} 189 | style={styles.container} 190 | navigationState={this.state} 191 | renderScene={this._renderScene} 192 | onIndexChange={this._handleIndexChange} 193 | swipeEnabled 194 | /> 195 | ); 196 | } 197 | } 198 | ``` 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /__setup__/enzyme.js: -------------------------------------------------------------------------------- 1 | import Enzyme from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | Enzyme.configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /__tests__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.eslintrc", 3 | "env": { 4 | "jest": true 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /__tests__/TabBarVertical.test.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { StyleSheet, View } from 'react-native'; 5 | import { shallow } from 'enzyme'; 6 | import TabViewVertical from '../src/TabViewVertical'; 7 | import TabBarVertical from '../src/TabBarVertical'; 8 | import { SceneMap } from '../src/index'; 9 | 10 | const styles = StyleSheet.create({ 11 | container: { 12 | flex: 1, 13 | }, 14 | tabbar: { 15 | backgroundColor: '#3f51b5', 16 | }, 17 | tab: { 18 | width: 180, 19 | height: 80, 20 | }, 21 | indicator: { 22 | backgroundColor: '#ffeb3b', 23 | }, 24 | label: { 25 | color: '#fff', 26 | fontWeight: '400', 27 | }, 28 | }); 29 | 30 | const state = { 31 | index: 1, 32 | routes: [ 33 | { key: 'article', title: 'Article' }, 34 | { key: 'contacts', title: 'Contacts' }, 35 | { key: 'albums', title: 'Albums' }, 36 | { key: 'chat', title: 'Chat' }, 37 | ], 38 | }; 39 | 40 | const _handleIndexChange = jest.fn(); 41 | 42 | const _renderTabBar = props => ( 43 | 51 | ); 52 | 53 | const _renderScene = SceneMap({ 54 | albums: View, 55 | contacts: View, 56 | article: View, 57 | chat: View, 58 | }); 59 | 60 | it('renders a vertical tab bar', () => { 61 | const component = shallow( 62 | 70 | ); 71 | expect(component).toMatchSnapshot(); 72 | }); 73 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/TabBarVertical.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders a vertical tab bar 1`] = ` 4 | 22 | 86 | 94 | 140 | 151 | 162 | 173 | 184 | 185 | 186 | 187 | `; 188 | -------------------------------------------------------------------------------- /example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "expo" 4 | ], 5 | "plugins": [ 6 | ["module-resolver", { 7 | "alias": { 8 | "react-native-tab-view": "../src" 9 | } 10 | }] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /example/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /example/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": '../.eslintrc', 3 | 4 | "settings": { 5 | "import/core-modules": [ "expo", "react-native-tab-view" ] 6 | }, 7 | 8 | "rules": { 9 | "react/prop-types": "off", 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /example/.expo/packager-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "expoServerPort": null, 3 | "packagerPort": null, 4 | "packagerPid": null, 5 | "expoServerNgrokUrl": null, 6 | "packagerNgrokUrl": null, 7 | "ngrokPid": null 8 | } -------------------------------------------------------------------------------- /example/.expo/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "hostType": "tunnel", 3 | "lanType": "ip", 4 | "dev": true, 5 | "strict": false, 6 | "minify": false, 7 | "urlType": "exp", 8 | "urlRandomness": "q2-h4g" 9 | } -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /example/App.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import './src/App'; 4 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | ## Run the example 2 | 3 | 4 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "React Native Vertical Tab View Demo", 4 | "description": "Demonstrates the various capabilities of react-native-vertical-tab-view", 5 | "slug": "react-native-vertical-tab-view-demo", 6 | "sdkVersion": "27.0.0", 7 | "version": "1.0.0", 8 | "primaryColor": "#2196f3", 9 | "icon": "assets/icon.png", 10 | "loading": { 11 | "icon": "assets/icon.png", 12 | "hideExponentText": false 13 | }, 14 | "packagerOpts": { 15 | "assetExts": [ 16 | "ttf" 17 | ], 18 | "config": "./rn-cli.config.js", 19 | "projectRoots": "" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/assets/VerticalTabView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaKaZ/react-native-vertical-tab-view/35702ae944b30ce922510691c84ed99748e7c9ff/example/assets/VerticalTabView.gif -------------------------------------------------------------------------------- /example/assets/album-art-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaKaZ/react-native-vertical-tab-view/35702ae944b30ce922510691c84ed99748e7c9ff/example/assets/album-art-1.jpg -------------------------------------------------------------------------------- /example/assets/album-art-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaKaZ/react-native-vertical-tab-view/35702ae944b30ce922510691c84ed99748e7c9ff/example/assets/album-art-2.jpg -------------------------------------------------------------------------------- /example/assets/album-art-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaKaZ/react-native-vertical-tab-view/35702ae944b30ce922510691c84ed99748e7c9ff/example/assets/album-art-3.jpg -------------------------------------------------------------------------------- /example/assets/album-art-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaKaZ/react-native-vertical-tab-view/35702ae944b30ce922510691c84ed99748e7c9ff/example/assets/album-art-4.jpg -------------------------------------------------------------------------------- /example/assets/album-art-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaKaZ/react-native-vertical-tab-view/35702ae944b30ce922510691c84ed99748e7c9ff/example/assets/album-art-5.jpg -------------------------------------------------------------------------------- /example/assets/album-art-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaKaZ/react-native-vertical-tab-view/35702ae944b30ce922510691c84ed99748e7c9ff/example/assets/album-art-6.jpg -------------------------------------------------------------------------------- /example/assets/album-art-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaKaZ/react-native-vertical-tab-view/35702ae944b30ce922510691c84ed99748e7c9ff/example/assets/album-art-7.jpg -------------------------------------------------------------------------------- /example/assets/album-art-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaKaZ/react-native-vertical-tab-view/35702ae944b30ce922510691c84ed99748e7c9ff/example/assets/album-art-8.jpg -------------------------------------------------------------------------------- /example/assets/avatar-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaKaZ/react-native-vertical-tab-view/35702ae944b30ce922510691c84ed99748e7c9ff/example/assets/avatar-1.png -------------------------------------------------------------------------------- /example/assets/avatar-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaKaZ/react-native-vertical-tab-view/35702ae944b30ce922510691c84ed99748e7c9ff/example/assets/avatar-2.png -------------------------------------------------------------------------------- /example/assets/book.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaKaZ/react-native-vertical-tab-view/35702ae944b30ce922510691c84ed99748e7c9ff/example/assets/book.jpg -------------------------------------------------------------------------------- /example/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaKaZ/react-native-vertical-tab-view/35702ae944b30ce922510691c84ed99748e7c9ff/example/assets/icon.png -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "verticalTabViewExample", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "App.js", 6 | "scripts": { 7 | "start": "react-native-scripts start", 8 | "android": "react-native-scripts android", 9 | "ios": "react-native-scripts ios" 10 | }, 11 | "dependencies": { 12 | "@expo/vector-icons": "^6.3.1", 13 | "expo": "~27.0.2", 14 | "react": "16.3.1", 15 | "react-native": "~0.55.4", 16 | "react-native-gesture-handler": "^1.0.1", 17 | "react-native-tab-view": "^1.0.2" 18 | }, 19 | "devDependencies": { 20 | "babel-plugin-module-resolver": "^3.1.1", 21 | "babel-preset-expo": "^4.0.0", 22 | "glob-to-regexp": "^0.4.0", 23 | "react-native-scripts": "1.13.1" 24 | }, 25 | 26 | "resolutions": { 27 | "**/react": "16.3.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/rn-cli.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-commonjs */ 2 | 3 | const path = require('path'); 4 | const glob = require('glob-to-regexp'); 5 | const blacklist = require('metro/src/blacklist'); 6 | const pak = require('../package.json'); 7 | 8 | const dependencies = Object.keys(pak.dependencies); 9 | const peerDependencies = Object.keys(pak.peerDependencies); 10 | 11 | module.exports = { 12 | getProjectRoots() { 13 | return [__dirname, path.resolve(__dirname, '..')]; 14 | }, 15 | getProvidesModuleNodeModules() { 16 | return [...dependencies, ...peerDependencies]; 17 | }, 18 | getBlacklistRE() { 19 | return blacklist([glob(`${path.resolve(__dirname, '..')}/node_modules/*`)]); 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /example/src/App.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { 5 | AsyncStorage, 6 | Platform, 7 | ScrollView, 8 | StatusBar, 9 | StyleSheet, 10 | Text, 11 | TouchableOpacity, 12 | View, 13 | } from 'react-native'; 14 | import { Ionicons } from '@expo/vector-icons'; 15 | import TopBarTextExample from './TopBarTextExample'; 16 | import TopBarIconExample from './TopBarIconExample'; 17 | import BottomBarIconTextExample from './BottomBarIconTextExample'; 18 | import NoAnimationExample from './NoAnimationExample'; 19 | import CoverflowExample from './CoverflowExample'; 20 | import NativeDriverExample from './NativeDriverExample'; 21 | import VerticalTabBarTextExample from './VerticalTabBarTextExample'; 22 | 23 | const PERSISTENCE_KEY = 'index_persistence'; 24 | 25 | const EXAMPLE_COMPONENTS = [ 26 | TopBarTextExample, 27 | TopBarIconExample, 28 | BottomBarIconTextExample, 29 | NoAnimationExample, 30 | CoverflowExample, 31 | NativeDriverExample, 32 | VerticalTabBarTextExample, 33 | ]; 34 | 35 | type State = { 36 | title: string, 37 | index: number, 38 | restoring: boolean, 39 | }; 40 | 41 | export default class ExampleList extends React.Component<{}, State> { 42 | state = { 43 | title: 'Examples', 44 | index: -1, 45 | restoring: false, 46 | }; 47 | 48 | componentDidMount() { 49 | if (process.env.NODE_ENV !== 'production') { 50 | this._restoreNavigationState(); 51 | } 52 | 53 | [ 54 | require('../assets/album-art-1.jpg'), 55 | require('../assets/album-art-2.jpg'), 56 | require('../assets/album-art-3.jpg'), 57 | require('../assets/album-art-4.jpg'), 58 | require('../assets/album-art-5.jpg'), 59 | require('../assets/album-art-6.jpg'), 60 | require('../assets/album-art-7.jpg'), 61 | require('../assets/album-art-8.jpg'), 62 | ].map(image => Expo.Asset.fromModule(image).downloadAsync()); 63 | } 64 | 65 | _persistNavigationState = async (currentIndex: number) => { 66 | if (process.env.NODE_ENV !== 'production') { 67 | await AsyncStorage.setItem(PERSISTENCE_KEY, JSON.stringify(currentIndex)); 68 | } 69 | }; 70 | 71 | _restoreNavigationState = async () => { 72 | this.setState({ 73 | restoring: true, 74 | }); 75 | 76 | const savedIndexString = await AsyncStorage.getItem(PERSISTENCE_KEY); 77 | 78 | try { 79 | const savedIndex = JSON.parse(savedIndexString); 80 | if (typeof savedIndex === 'number' && !isNaN(savedIndex)) { 81 | this.setState({ 82 | index: savedIndex, 83 | }); 84 | } 85 | } catch (e) { 86 | // ignore 87 | } 88 | 89 | this.setState({ 90 | restoring: false, 91 | }); 92 | }; 93 | 94 | _handleNavigate = index => { 95 | this.setState({ 96 | index, 97 | }); 98 | this._persistNavigationState(index); 99 | }; 100 | 101 | _handleNavigateBack = () => { 102 | this._handleNavigate(-1); 103 | }; 104 | 105 | _renderItem = (component, i) => { 106 | return ( 107 | this._handleNavigate(i)} 111 | > 112 | 113 | {i + 1}. {component.title} 114 | 115 | 116 | ); 117 | }; 118 | 119 | render() { 120 | if (this.state.restoring) { 121 | return null; 122 | } 123 | 124 | const { index } = this.state; 125 | 126 | const ExampleComponent = EXAMPLE_COMPONENTS[index] || null; 127 | const backgroundColor = 128 | ExampleComponent && ExampleComponent.backgroundColor 129 | ? ExampleComponent.backgroundColor 130 | : '#111'; 131 | const tintColor = 132 | ExampleComponent && ExampleComponent.tintColor 133 | ? ExampleComponent.tintColor 134 | : 'white'; 135 | const appbarElevation = 136 | ExampleComponent && Number.isFinite(ExampleComponent.appbarElevation) 137 | ? ExampleComponent.appbarElevation 138 | : 4; 139 | const statusBarStyle = 140 | ExampleComponent && ExampleComponent.statusBarStyle 141 | ? ExampleComponent.statusBarStyle 142 | : 'light-content'; 143 | const borderBottomWidth = 144 | Platform.OS === 'ios' ? StyleSheet.hairlineWidth : 0; 145 | 146 | return ( 147 | 148 | 154 | 155 | 161 | 170 | {index > -1 ? ( 171 | 175 | 182 | 183 | ) : null} 184 | 185 | {index > -1 ? EXAMPLE_COMPONENTS[index].title : this.state.title} 186 | 187 | {index > -1 ? : null} 188 | 189 | {index === -1 ? ( 190 | {EXAMPLE_COMPONENTS.map(this._renderItem)} 191 | ) : ExampleComponent ? ( 192 | 193 | ) : null} 194 | 195 | ); 196 | } 197 | } 198 | 199 | const styles = StyleSheet.create({ 200 | container: { 201 | flex: 1, 202 | backgroundColor: '#eceff1', 203 | }, 204 | statusbar: { 205 | height: Platform.OS === 'ios' ? 20 : 25, 206 | }, 207 | appbar: { 208 | flexDirection: 'row', 209 | alignItems: 'center', 210 | height: Platform.OS === 'ios' ? 44 : 56, 211 | borderBottomColor: 'rgba(0, 0, 0, 0.1)', 212 | }, 213 | title: { 214 | flex: 1, 215 | margin: 16, 216 | textAlign: Platform.OS === 'ios' ? 'center' : 'left', 217 | fontSize: 18, 218 | color: '#fff', 219 | }, 220 | button: { 221 | flexDirection: 'row', 222 | alignItems: 'center', 223 | width: 56, 224 | padding: Platform.OS === 'ios' ? 12 : 16, 225 | }, 226 | touchable: { 227 | padding: 16, 228 | backgroundColor: '#fff', 229 | borderBottomWidth: 1, 230 | borderBottomColor: 'rgba(0, 0, 0, .06)', 231 | }, 232 | item: { 233 | fontSize: 16, 234 | color: '#333', 235 | }, 236 | }); 237 | 238 | Expo.registerRootComponent(ExampleList); 239 | -------------------------------------------------------------------------------- /example/src/BottomBarIconTextExample.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { Animated, View, Text, StyleSheet } from 'react-native'; 5 | import { Ionicons } from '@expo/vector-icons'; 6 | import { 7 | TabView, 8 | TabBar, 9 | SceneMap, 10 | type Route, 11 | type NavigationState, 12 | } from 'react-native-tab-view'; 13 | import Albums from './shared/Albums'; 14 | import Article from './shared/Article'; 15 | import Contacts from './shared/Contacts'; 16 | 17 | type State = NavigationState< 18 | Route<{ 19 | key: string, 20 | icon: string, 21 | color: string, 22 | }> 23 | >; 24 | 25 | export default class BottomBarIconExample extends React.Component<*, State> { 26 | static title = 'Custom indicator'; 27 | static backgroundColor = '#263238'; 28 | static appbarElevation = 4; 29 | 30 | state = { 31 | index: 0, 32 | routes: [ 33 | { 34 | key: 'article', 35 | icon: 'ios-paper', 36 | color: '#F44336', 37 | }, 38 | { 39 | key: 'contacts', 40 | icon: 'ios-people', 41 | color: '#3F51B5', 42 | }, 43 | { 44 | key: 'albums', 45 | icon: 'ios-albums', 46 | color: '#4CAF50', 47 | }, 48 | ], 49 | }; 50 | 51 | _handleIndexChange = index => 52 | this.setState({ 53 | index, 54 | }); 55 | 56 | _renderIndicator = props => { 57 | const { width, position } = props; 58 | const inputRange = [ 59 | 0, 60 | 0.48, 61 | 0.49, 62 | 0.51, 63 | 0.52, 64 | 1, 65 | 1.48, 66 | 1.49, 67 | 1.51, 68 | 1.52, 69 | 2, 70 | ]; 71 | 72 | const scale = position.interpolate({ 73 | inputRange, 74 | outputRange: inputRange.map(x => (Math.trunc(x) === x ? 2 : 0.1)), 75 | }); 76 | const opacity = position.interpolate({ 77 | inputRange, 78 | outputRange: inputRange.map(x => { 79 | const d = x - Math.trunc(x); 80 | return d === 0.49 || d === 0.51 ? 0 : 1; 81 | }), 82 | }); 83 | const translateX = position.interpolate({ 84 | inputRange: inputRange, 85 | outputRange: inputRange.map(x => Math.round(x) * width), 86 | }); 87 | const backgroundColor = position.interpolate({ 88 | inputRange, 89 | outputRange: inputRange.map( 90 | x => props.navigationState.routes[Math.round(x)].color 91 | ), 92 | }); 93 | 94 | return ( 95 | 98 | 104 | 105 | ); 106 | }; 107 | 108 | _renderIcon = ({ route }) => ( 109 | 110 | ); 111 | 112 | _renderBadge = ({ route }) => { 113 | if (route.key === '2') { 114 | return ( 115 | 116 | 42 117 | 118 | ); 119 | } 120 | return null; 121 | }; 122 | 123 | _renderTabBar = props => ( 124 | 131 | ); 132 | 133 | _renderScene = SceneMap({ 134 | article: Article, 135 | contacts: Contacts, 136 | albums: Albums, 137 | }); 138 | 139 | render() { 140 | return ( 141 | 149 | ); 150 | } 151 | } 152 | 153 | const styles = StyleSheet.create({ 154 | tabbar: { 155 | backgroundColor: '#263238', 156 | overflow: 'hidden', 157 | }, 158 | icon: { 159 | backgroundColor: 'transparent', 160 | color: 'white', 161 | }, 162 | container: { 163 | flex: 1, 164 | alignItems: 'center', 165 | justifyContent: 'center', 166 | opacity: 0.8, 167 | }, 168 | indicator: { 169 | width: 48, 170 | height: 48, 171 | borderRadius: 24, 172 | backgroundColor: '#0084ff', 173 | margin: 6, 174 | }, 175 | badge: { 176 | marginTop: 4, 177 | marginRight: 32, 178 | backgroundColor: '#f44336', 179 | height: 24, 180 | width: 24, 181 | borderRadius: 12, 182 | alignItems: 'center', 183 | justifyContent: 'center', 184 | elevation: 4, 185 | }, 186 | count: { 187 | color: '#fff', 188 | fontSize: 12, 189 | fontWeight: 'bold', 190 | marginTop: -2, 191 | }, 192 | }); 193 | -------------------------------------------------------------------------------- /example/src/CoverflowExample.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { 5 | Animated, 6 | View, 7 | Image, 8 | Text, 9 | Dimensions, 10 | StyleSheet, 11 | } from 'react-native'; 12 | import { 13 | TabView, 14 | PagerPan, 15 | type Route, 16 | type NavigationState, 17 | } from 'react-native-tab-view'; 18 | 19 | type State = NavigationState< 20 | Route<{ 21 | key: string, 22 | }> 23 | >; 24 | 25 | const ALBUMS = { 26 | 'Abbey Road': require('../assets/album-art-1.jpg'), 27 | 'Bat Out of Hell': require('../assets/album-art-2.jpg'), 28 | Homogenic: require('../assets/album-art-3.jpg'), 29 | 'Number of the Beast': require('../assets/album-art-4.jpg'), 30 | "It's Blitz": require('../assets/album-art-5.jpg'), 31 | 'The Man-Machine': require('../assets/album-art-6.jpg'), 32 | 'The Score': require('../assets/album-art-7.jpg'), 33 | 'Lost Horizons': require('../assets/album-art-8.jpg'), 34 | }; 35 | 36 | const initialLayout = { 37 | height: 0, 38 | width: Dimensions.get('window').width, 39 | }; 40 | 41 | export default class CoverflowExample extends React.Component<*, State> { 42 | static title = 'Coverflow'; 43 | static backgroundColor = '#000'; 44 | static appbarElevation = 0; 45 | 46 | state = { 47 | index: 2, 48 | routes: Object.keys(ALBUMS).map(key => ({ key })), 49 | }; 50 | 51 | _buildCoverFlowStyle = ({ layout, position, route, navigationState }) => { 52 | const { width } = layout; 53 | const { routes } = navigationState; 54 | const currentIndex = routes.indexOf(route); 55 | const inputRange = routes.map((x, i) => i); 56 | const translateOutputRange = inputRange.map(i => { 57 | return (width / 2) * (currentIndex - i) * -1; 58 | }); 59 | const scaleOutputRange = inputRange.map(i => { 60 | if (currentIndex === i) { 61 | return 1; 62 | } else { 63 | return 0.7; 64 | } 65 | }); 66 | const opacityOutputRange = inputRange.map(i => { 67 | if (currentIndex === i) { 68 | return 1; 69 | } else { 70 | return 0.3; 71 | } 72 | }); 73 | 74 | const translateX = position.interpolate({ 75 | inputRange, 76 | outputRange: translateOutputRange, 77 | extrapolate: 'clamp', 78 | }); 79 | const scale = position.interpolate({ 80 | inputRange, 81 | outputRange: scaleOutputRange, 82 | extrapolate: 'clamp', 83 | }); 84 | const opacity = position.interpolate({ 85 | inputRange, 86 | outputRange: opacityOutputRange, 87 | extrapolate: 'clamp', 88 | }); 89 | 90 | return { 91 | transform: [{ translateX }, { scale }], 92 | opacity, 93 | }; 94 | }; 95 | 96 | _handleIndexChange = index => 97 | this.setState({ 98 | index, 99 | }); 100 | 101 | _renderTabBar = () => null; 102 | 103 | _renderScene = props => ( 104 | 105 | 106 | 107 | 108 | {props.route.key} 109 | 110 | ); 111 | 112 | _renderPager = props => ; 113 | 114 | render() { 115 | return ( 116 | 125 | ); 126 | } 127 | } 128 | 129 | const styles = StyleSheet.create({ 130 | container: { 131 | flex: 1, 132 | backgroundColor: '#000', 133 | }, 134 | page: { 135 | flex: 1, 136 | alignItems: 'center', 137 | justifyContent: 'center', 138 | }, 139 | album: { 140 | backgroundColor: '#000', 141 | width: 200, 142 | height: 200, 143 | elevation: 12, 144 | shadowColor: '#000000', 145 | shadowOpacity: 0.5, 146 | shadowRadius: 8, 147 | shadowOffset: { 148 | height: 8, 149 | }, 150 | }, 151 | cover: { 152 | width: 200, 153 | height: 200, 154 | }, 155 | label: { 156 | margin: 16, 157 | color: '#fff', 158 | }, 159 | }); 160 | -------------------------------------------------------------------------------- /example/src/NativeDriverExample.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { StyleSheet, Dimensions } from 'react-native'; 5 | import { 6 | TabView, 7 | TabBar, 8 | PagerExperimental, 9 | SceneMap, 10 | type Route, 11 | type NavigationState, 12 | } from 'react-native-tab-view'; 13 | import * as GestureHandler from 'react-native-gesture-handler'; 14 | import Albums from './shared/Albums'; 15 | import Article from './shared/Article'; 16 | import Chat from './shared/Chat'; 17 | 18 | type State = NavigationState< 19 | Route<{ 20 | key: string, 21 | title: string, 22 | }> 23 | >; 24 | 25 | const initialLayout = { 26 | height: 0, 27 | width: Dimensions.get('window').width, 28 | }; 29 | 30 | export default class NativeDriverExample extends React.Component<*, State> { 31 | static title = 'Native animations'; 32 | static backgroundColor = '#f44336'; 33 | static appbarElevation = 0; 34 | 35 | state = { 36 | index: 1, 37 | routes: [ 38 | { key: 'article', title: 'Article' }, 39 | { key: 'albums', title: 'Albums' }, 40 | { key: 'chat', title: 'Chat' }, 41 | ], 42 | }; 43 | 44 | _handleIndexChange = index => 45 | this.setState({ 46 | index, 47 | }); 48 | 49 | _renderTabBar = props => ( 50 | 51 | ); 52 | 53 | _renderScene = SceneMap({ 54 | article: Article, 55 | albums: Albums, 56 | chat: Chat, 57 | }); 58 | 59 | _renderPager = props => ( 60 | 61 | ); 62 | 63 | render() { 64 | return ( 65 | 75 | ); 76 | } 77 | } 78 | 79 | const styles = StyleSheet.create({ 80 | container: { 81 | flex: 1, 82 | }, 83 | tabbar: { 84 | backgroundColor: '#f44336', 85 | }, 86 | label: { 87 | color: '#fff', 88 | fontWeight: '400', 89 | }, 90 | }); 91 | -------------------------------------------------------------------------------- /example/src/NoAnimationExample.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { 5 | Animated, 6 | View, 7 | TouchableWithoutFeedback, 8 | StyleSheet, 9 | } from 'react-native'; 10 | import { 11 | TabView, 12 | SceneMap, 13 | type Route, 14 | type NavigationState, 15 | } from 'react-native-tab-view'; 16 | import { Ionicons } from '@expo/vector-icons'; 17 | import Albums from './shared/Albums'; 18 | import Article from './shared/Article'; 19 | import Chat from './shared/Chat'; 20 | import Contacts from './shared/Contacts'; 21 | 22 | const AnimatedIcon = Animated.createAnimatedComponent(Ionicons); 23 | 24 | type State = NavigationState< 25 | Route<{ 26 | key: string, 27 | title: string, 28 | icon: string, 29 | }> 30 | >; 31 | 32 | export default class TopBarIconExample extends React.Component<*, State> { 33 | static title = 'No animation'; 34 | static backgroundColor = '#fafafa'; 35 | static tintColor = '#263238'; 36 | static appbarElevation = 4; 37 | static statusBarStyle = 'dark-content'; 38 | 39 | state = { 40 | index: 0, 41 | routes: [ 42 | { key: 'contacts', title: 'Contacts', icon: 'ios-people' }, 43 | { key: 'albums', title: 'Albums', icon: 'ios-albums' }, 44 | { key: 'article', title: 'Article', icon: 'ios-paper' }, 45 | { key: 'chat', title: 'Chat', icon: 'ios-chatboxes' }, 46 | ], 47 | }; 48 | 49 | _handleIndexChange = index => 50 | this.setState({ 51 | index, 52 | }); 53 | 54 | _renderLabel = ({ position, navigationState }) => ({ route, index }) => { 55 | const inputRange = navigationState.routes.map((x, i) => i); 56 | const outputRange = inputRange.map( 57 | inputIndex => (inputIndex === index ? '#2196f3' : '#939393') 58 | ); 59 | const color = position.interpolate({ 60 | inputRange, 61 | outputRange, 62 | }); 63 | return ( 64 | 65 | {route.title} 66 | 67 | ); 68 | }; 69 | 70 | _renderIcon = ({ navigationState, position }) => ({ route, index }) => { 71 | const inputRange = navigationState.routes.map((x, i) => i); 72 | const filledOpacity = position.interpolate({ 73 | inputRange, 74 | outputRange: inputRange.map(i => (i === index ? 1 : 0)), 75 | }); 76 | const outlineOpacity = position.interpolate({ 77 | inputRange, 78 | outputRange: inputRange.map(i => (i === index ? 0 : 1)), 79 | }); 80 | return ( 81 | 82 | 87 | 92 | 93 | ); 94 | }; 95 | 96 | _renderTabBar = props => ( 97 | 98 | {props.navigationState.routes.map((route, index) => { 99 | return ( 100 | props.jumpTo(route.key)} 103 | > 104 | 105 | {this._renderIcon(props)({ route, index })} 106 | {this._renderLabel(props)({ route, index })} 107 | 108 | 109 | ); 110 | })} 111 | 112 | ); 113 | 114 | _renderScene = SceneMap({ 115 | contacts: Contacts, 116 | albums: Albums, 117 | article: Article, 118 | chat: Chat, 119 | }); 120 | 121 | render() { 122 | return ( 123 | 133 | ); 134 | } 135 | } 136 | 137 | const styles = StyleSheet.create({ 138 | container: { 139 | flex: 1, 140 | }, 141 | tabbar: { 142 | flexDirection: 'row', 143 | justifyContent: 'space-between', 144 | backgroundColor: '#fafafa', 145 | }, 146 | tab: { 147 | flex: 1, 148 | alignItems: 'center', 149 | borderTopWidth: StyleSheet.hairlineWidth, 150 | borderTopColor: 'rgba(0, 0, 0, .2)', 151 | paddingTop: 4.5, 152 | }, 153 | iconContainer: { 154 | height: 26, 155 | width: 26, 156 | }, 157 | icon: { 158 | position: 'absolute', 159 | textAlign: 'center', 160 | top: 0, 161 | left: 0, 162 | right: 0, 163 | bottom: 0, 164 | color: '#0084ff', 165 | }, 166 | outline: { 167 | color: '#939393', 168 | }, 169 | label: { 170 | fontSize: 10, 171 | marginTop: 3, 172 | marginBottom: 1.5, 173 | backgroundColor: 'transparent', 174 | }, 175 | }); 176 | -------------------------------------------------------------------------------- /example/src/TopBarIconExample.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { StyleSheet } from 'react-native'; 5 | import { Ionicons } from '@expo/vector-icons'; 6 | import { 7 | TabView, 8 | TabBar, 9 | SceneMap, 10 | type Route, 11 | type NavigationState, 12 | } from 'react-native-tab-view'; 13 | import Article from './shared/Article'; 14 | import Chat from './shared/Chat'; 15 | import Contacts from './shared/Contacts'; 16 | 17 | type State = NavigationState< 18 | Route<{ 19 | key: string, 20 | icon: string, 21 | }> 22 | >; 23 | 24 | export default class TopBarIconExample extends React.Component<*, State> { 25 | static title = 'Icon only top bar'; 26 | static backgroundColor = '#e91e63'; 27 | static appbarElevation = 0; 28 | 29 | state = { 30 | index: 0, 31 | routes: [ 32 | { key: 'chat', icon: 'md-chatbubbles' }, 33 | { key: 'contacts', icon: 'md-contact' }, 34 | { key: 'article', icon: 'md-list' }, 35 | ], 36 | }; 37 | 38 | _handleIndexChange = index => 39 | this.setState({ 40 | index, 41 | }); 42 | 43 | _renderIcon = ({ route }) => ( 44 | 45 | ); 46 | 47 | _renderTabBar = props => { 48 | return ( 49 | 55 | ); 56 | }; 57 | 58 | _renderScene = SceneMap({ 59 | chat: Chat, 60 | contacts: Contacts, 61 | article: Article, 62 | }); 63 | 64 | render() { 65 | return ( 66 | 73 | ); 74 | } 75 | } 76 | 77 | const styles = StyleSheet.create({ 78 | container: { 79 | flex: 1, 80 | }, 81 | tabbar: { 82 | backgroundColor: '#e91e63', 83 | }, 84 | indicator: { 85 | backgroundColor: '#ffeb3b', 86 | }, 87 | }); 88 | -------------------------------------------------------------------------------- /example/src/TopBarTextExample.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { StyleSheet, Dimensions } from 'react-native'; 5 | import { 6 | TabView, 7 | TabBar, 8 | SceneMap, 9 | type Route, 10 | type NavigationState, 11 | } from 'react-native-tab-view'; 12 | import Article from './shared/Article'; 13 | import Albums from './shared/Albums'; 14 | import Chat from './shared/Chat'; 15 | import Contacts from './shared/Contacts'; 16 | 17 | console.log('scenemap:', SceneMap); 18 | 19 | type State = NavigationState< 20 | Route<{ 21 | key: string, 22 | title: string, 23 | }> 24 | >; 25 | 26 | const initialLayout = { 27 | height: 0, 28 | width: Dimensions.get('window').width, 29 | }; 30 | 31 | export default class TopBarTextExample extends React.Component<*, State> { 32 | static title = 'Scrollable top bar'; 33 | static backgroundColor = '#3f51b5'; 34 | static appbarElevation = 0; 35 | 36 | state = { 37 | index: 1, 38 | routes: [ 39 | { key: 'article', title: 'Article' }, 40 | { key: 'contacts', title: 'Contacts' }, 41 | { key: 'albums', title: 'Albums' }, 42 | { key: 'chat', title: 'Chat' }, 43 | ], 44 | }; 45 | 46 | _handleIndexChange = index => 47 | this.setState({ 48 | index, 49 | }); 50 | 51 | _renderTabBar = props => ( 52 | 60 | ); 61 | 62 | _renderScene = SceneMap({ 63 | albums: Albums, 64 | contacts: Contacts, 65 | article: Article, 66 | chat: Chat, 67 | }); 68 | 69 | render() { 70 | return ( 71 | { 75 | console.log('calling renderScene with', props); 76 | return this._renderScene(props); 77 | }} 78 | renderTabBar={this._renderTabBar} 79 | onIndexChange={this._handleIndexChange} 80 | initialLayout={initialLayout} 81 | /> 82 | ); 83 | } 84 | } 85 | 86 | const styles = StyleSheet.create({ 87 | container: { 88 | flex: 1, 89 | }, 90 | tabbar: { 91 | backgroundColor: '#3f51b5', 92 | }, 93 | tab: { 94 | width: 320, 95 | }, 96 | indicator: { 97 | backgroundColor: '#ffeb3b', 98 | }, 99 | label: { 100 | color: '#fff', 101 | fontWeight: '400', 102 | }, 103 | }); 104 | -------------------------------------------------------------------------------- /example/src/VerticalTabBarTextExample.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { StyleSheet, Dimensions } from 'react-native'; 5 | import { 6 | TabBarVertical, 7 | TabViewVertical, 8 | SceneMap, 9 | type Route, 10 | type NavigationState, 11 | } from 'react-native-vertical-tab-view'; 12 | import Article from './shared/Article'; 13 | import Albums from './shared/Albums'; 14 | import Chat from './shared/Chat'; 15 | import Contacts from './shared/Contacts'; 16 | 17 | type State = NavigationState< 18 | Route<{ 19 | key: string, 20 | title: string, 21 | }> 22 | >; 23 | 24 | const initialLayout = { 25 | height: 0, 26 | width: Dimensions.get('window').width, 27 | }; 28 | 29 | export default class VerticalTabBarTextExample extends React.Component< 30 | *, 31 | State 32 | > { 33 | static title = 'Scrollable left vertical bar'; 34 | static backgroundColor = '#3f51b5'; 35 | static appbarElevation = 0; 36 | 37 | state = { 38 | index: 1, 39 | routes: [ 40 | { key: 'article', title: 'Article' }, 41 | { key: 'contacts', title: 'Contacts' }, 42 | { key: 'albums', title: 'Albums' }, 43 | { key: 'chat', title: 'Chat' }, 44 | ], 45 | }; 46 | 47 | _handleIndexChange = index => 48 | this.setState({ 49 | index, 50 | }); 51 | 52 | _renderTabBar = props => ( 53 | 61 | ); 62 | 63 | _renderScene = SceneMap({ 64 | albums: Albums, 65 | contacts: Contacts, 66 | article: Article, 67 | chat: Chat, 68 | }); 69 | 70 | render() { 71 | return ( 72 | 80 | ); 81 | } 82 | } 83 | 84 | const styles = StyleSheet.create({ 85 | container: { 86 | flex: 1, 87 | }, 88 | tabbar: { 89 | backgroundColor: '#3f51b5', 90 | }, 91 | tab: { 92 | width: 180, 93 | height: 80, 94 | }, 95 | indicator: { 96 | backgroundColor: '#ffeb3b', 97 | }, 98 | label: { 99 | color: '#fff', 100 | fontWeight: '400', 101 | }, 102 | }); 103 | -------------------------------------------------------------------------------- /example/src/shared/Albums.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /* eslint-disable import/no-commonjs */ 3 | 4 | import * as React from 'react'; 5 | import { ScrollView, Image, Dimensions, StyleSheet } from 'react-native'; 6 | 7 | const COVERS = [ 8 | require('../../assets/album-art-1.jpg'), 9 | require('../../assets/album-art-2.jpg'), 10 | require('../../assets/album-art-3.jpg'), 11 | require('../../assets/album-art-4.jpg'), 12 | require('../../assets/album-art-5.jpg'), 13 | require('../../assets/album-art-6.jpg'), 14 | require('../../assets/album-art-7.jpg'), 15 | require('../../assets/album-art-8.jpg'), 16 | ]; 17 | 18 | export default class Albums extends React.Component<*> { 19 | render() { 20 | return ( 21 | 25 | {COVERS.map((source, i) => ( 26 | 27 | ))} 28 | 29 | ); 30 | } 31 | } 32 | 33 | const styles = StyleSheet.create({ 34 | container: { 35 | backgroundColor: '#343C46', 36 | }, 37 | content: { 38 | flexDirection: 'row', 39 | flexWrap: 'wrap', 40 | }, 41 | cover: { 42 | width: '50%', 43 | height: Dimensions.get('window').width / 2, 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /example/src/shared/Article.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { ScrollView, View, Text, Image, StyleSheet } from 'react-native'; 5 | 6 | export default class Article extends React.Component<*> { 7 | render() { 8 | return ( 9 | 13 | 14 | 18 | 19 | Knowledge Bot 20 | 1st Jan 2025 21 | 22 | 23 | Lorem Ipsum 24 | 25 | Contrary to popular belief, Lorem Ipsum is not simply random text. It 26 | has roots in a piece of classical Latin literature from 45 BC, making 27 | it over 2000 years old. 28 | 29 | 30 | 31 | Richard McClintock, a Latin professor at Hampden-Sydney College in 32 | Virginia, looked up one of the more obscure Latin words, consectetur, 33 | from a Lorem Ipsum passage, and going through the cites of the word in 34 | classical literature, discovered the undoubtable source. 35 | 36 | 37 | Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus 38 | Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written 39 | in 45 BC. This book is a treatise on the theory of ethics, very 40 | popular during the Renaissance. The first line of Lorem Ipsum, "Lorem 41 | ipsum dolor sit amet..", comes from a line in section 1.10.32. 42 | 43 | 44 | ); 45 | } 46 | } 47 | 48 | const styles = StyleSheet.create({ 49 | container: { 50 | backgroundColor: 'white', 51 | }, 52 | content: { 53 | paddingVertical: 16, 54 | }, 55 | author: { 56 | flexDirection: 'row', 57 | marginVertical: 8, 58 | marginHorizontal: 16, 59 | }, 60 | meta: { 61 | marginHorizontal: 8, 62 | justifyContent: 'center', 63 | }, 64 | name: { 65 | color: '#000', 66 | fontWeight: 'bold', 67 | fontSize: 16, 68 | lineHeight: 24, 69 | }, 70 | timestamp: { 71 | color: '#999', 72 | fontSize: 14, 73 | lineHeight: 21, 74 | }, 75 | avatar: { 76 | height: 48, 77 | width: 48, 78 | borderRadius: 24, 79 | }, 80 | title: { 81 | color: '#000', 82 | fontWeight: 'bold', 83 | fontSize: 36, 84 | marginVertical: 8, 85 | marginHorizontal: 16, 86 | }, 87 | paragraph: { 88 | color: '#000', 89 | fontSize: 16, 90 | lineHeight: 24, 91 | marginVertical: 8, 92 | marginHorizontal: 16, 93 | }, 94 | image: { 95 | width: '100%', 96 | height: 200, 97 | resizeMode: 'cover', 98 | marginVertical: 8, 99 | }, 100 | }); 101 | -------------------------------------------------------------------------------- /example/src/shared/Chat.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { 5 | ScrollView, 6 | View, 7 | Image, 8 | Text, 9 | TextInput, 10 | StyleSheet, 11 | } from 'react-native'; 12 | 13 | const MESSAGES = [ 14 | 'okay', 15 | 'sudo make me a sandwich', 16 | 'what? make it yourself', 17 | 'make me a sandwich', 18 | ]; 19 | 20 | export default class Albums extends React.Component<*> { 21 | render() { 22 | return ( 23 | 24 | 28 | {MESSAGES.map((text, i) => { 29 | const odd = i % 2; 30 | 31 | return ( 32 | 36 | 44 | 47 | 48 | {text} 49 | 50 | 51 | 52 | ); 53 | })} 54 | 55 | 60 | 61 | ); 62 | } 63 | } 64 | 65 | const styles = StyleSheet.create({ 66 | container: { 67 | flex: 1, 68 | backgroundColor: '#eceff1', 69 | }, 70 | inverted: { 71 | transform: [{ scaleY: -1 }], 72 | }, 73 | content: { 74 | padding: 16, 75 | }, 76 | even: { 77 | flexDirection: 'row', 78 | }, 79 | odd: { 80 | flexDirection: 'row-reverse', 81 | }, 82 | avatar: { 83 | marginVertical: 8, 84 | marginHorizontal: 6, 85 | height: 40, 86 | width: 40, 87 | borderRadius: 20, 88 | borderColor: 'rgba(0, 0, 0, .16)', 89 | borderWidth: StyleSheet.hairlineWidth, 90 | }, 91 | bubble: { 92 | marginVertical: 8, 93 | marginHorizontal: 6, 94 | paddingVertical: 12, 95 | paddingHorizontal: 16, 96 | borderRadius: 20, 97 | }, 98 | sent: { 99 | backgroundColor: '#cfd8dc', 100 | }, 101 | received: { 102 | backgroundColor: '#2196F3', 103 | }, 104 | sentText: { 105 | color: 'black', 106 | }, 107 | receivedText: { 108 | color: 'white', 109 | }, 110 | input: { 111 | height: 48, 112 | paddingVertical: 12, 113 | paddingHorizontal: 24, 114 | backgroundColor: 'white', 115 | }, 116 | }); 117 | -------------------------------------------------------------------------------- /example/src/shared/Contacts.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { View, Text, FlatList, StyleSheet } from 'react-native'; 5 | 6 | const CONTACTS = [ 7 | { name: 'Marissa Castillo', number: 7766398169 }, 8 | { name: 'Denzel Curry', number: 9394378449 }, 9 | { name: 'Miles Ferguson', number: 8966872888 }, 10 | { name: 'Desiree Webster', number: 6818656371 }, 11 | { name: 'Samantha Young', number: 6538288534 }, 12 | { name: 'Irene Hunter', number: 2932176249 }, 13 | { name: 'Annie Ryan', number: 4718456627 }, 14 | { name: 'Sasha Oliver', number: 9743195919 }, 15 | { name: 'Jarrod Avila', number: 8339212305 }, 16 | { name: 'Griffin Weaver', number: 6059349721 }, 17 | { name: 'Emilee Moss', number: 7382905180 }, 18 | { name: 'Angelique Oliver', number: 9689298436 }, 19 | { name: 'Emanuel Little', number: 6673376805 }, 20 | { name: 'Wayne Day', number: 6918839582 }, 21 | { name: 'Lauren Reese', number: 4652613201 }, 22 | { name: 'Kailey Ward', number: 2232609512 }, 23 | { name: 'Gabrielle Newman', number: 2837997127 }, 24 | { name: 'Luke Strickland', number: 8404732322 }, 25 | { name: 'Payton Garza', number: 7916140875 }, 26 | { name: 'Anna Moss', number: 3504954657 }, 27 | { name: 'Kailey Vazquez', number: 3002136330 }, 28 | { name: 'Jennifer Coleman', number: 5469629753 }, 29 | { name: 'Cindy Casey', number: 8446175026 }, 30 | { name: 'Dillon Doyle', number: 5614510703 }, 31 | { name: 'Savannah Garcia', number: 5634775094 }, 32 | { name: 'Kailey Hudson', number: 3289239675 }, 33 | { name: 'Ariel Green', number: 2103492196 }, 34 | { name: 'Weston Perez', number: 2984221823 }, 35 | { name: 'Kari Juarez', number: 9502125065 }, 36 | { name: 'Sara Sanders', number: 7696668206 }, 37 | { name: 'Griffin Le', number: 3396937040 }, 38 | { name: 'Fernando Valdez', number: 9124257306 }, 39 | { name: 'Taylor Marshall', number: 9656072372 }, 40 | { name: 'Elias Dunn', number: 9738536473 }, 41 | { name: 'Diane Barrett', number: 6886824829 }, 42 | { name: 'Samuel Freeman', number: 5523948094 }, 43 | { name: 'Irene Garza', number: 2077694008 }, 44 | { name: 'Devante Alvarez', number: 9897002645 }, 45 | { name: 'Sydney Floyd', number: 6462897254 }, 46 | { name: 'Toni Dixon', number: 3775448213 }, 47 | { name: 'Anastasia Spencer', number: 4548212752 }, 48 | { name: 'Reid Cortez', number: 6668056507 }, 49 | { name: 'Ramon Duncan', number: 8889157751 }, 50 | { name: 'Kenny Moreno', number: 5748219540 }, 51 | { name: 'Shelby Craig', number: 9473708675 }, 52 | { name: 'Jordyn Brewer', number: 7552277991 }, 53 | { name: 'Tanya Walker', number: 4308189657 }, 54 | { name: 'Nolan Figueroa', number: 9173443776 }, 55 | { name: 'Sophia Gibbs', number: 6435942770 }, 56 | { name: 'Vincent Sandoval', number: 2606111495 }, 57 | ]; 58 | 59 | export default class Contacts extends React.Component<*> { 60 | _renderItem = ({ item }) => ( 61 | 62 | 63 | {item.name.slice(0, 1).toUpperCase()} 64 | 65 | 66 | {item.name} 67 | {item.number} 68 | 69 | 70 | ); 71 | 72 | _ItemSeparator = () => ; 73 | 74 | render() { 75 | return ( 76 | String(i)} 79 | renderItem={this._renderItem} 80 | ItemSeparatorComponent={this._ItemSeparator} 81 | /> 82 | ); 83 | } 84 | } 85 | 86 | const styles = StyleSheet.create({ 87 | item: { 88 | backgroundColor: 'white', 89 | flexDirection: 'row', 90 | alignItems: 'center', 91 | padding: 8, 92 | }, 93 | avatar: { 94 | height: 36, 95 | width: 36, 96 | borderRadius: 18, 97 | backgroundColor: '#e91e63', 98 | alignItems: 'center', 99 | justifyContent: 'center', 100 | }, 101 | letter: { 102 | color: 'white', 103 | fontWeight: 'bold', 104 | }, 105 | details: { 106 | margin: 8, 107 | }, 108 | name: { 109 | fontWeight: 'bold', 110 | fontSize: 14, 111 | color: 'black', 112 | }, 113 | number: { 114 | fontSize: 12, 115 | color: '#999', 116 | }, 117 | separator: { 118 | height: StyleSheet.hairlineWidth, 119 | backgroundColor: 'rgba(0, 0, 0, .08)', 120 | }, 121 | }); 122 | -------------------------------------------------------------------------------- /example/src/shared/Profile.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { ScrollView, View, Text, Image, StyleSheet } from 'react-native'; 5 | 6 | export default class Profile extends React.Component<*> { 7 | render() { 8 | return ( 9 | 13 | 14 | 18 | 19 | Knowledge Bot 20 | 1st Jan 2025 21 | 22 | 23 | Lorem Ipsum 24 | 25 | Contrary to popular belief, Lorem Ipsum is not simply random text. It 26 | has roots in a piece of classical Latin literature from 45 BC, making 27 | it over 2000 years old. 28 | 29 | 30 | 31 | Richard McClintock, a Latin professor at Hampden-Sydney College in 32 | Virginia, looked up one of the more obscure Latin words, consectetur, 33 | from a Lorem Ipsum passage, and going through the cites of the word in 34 | classical literature, discovered the undoubtable source. 35 | 36 | 37 | Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus 38 | Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written 39 | in 45 BC. This book is a treatise on the theory of ethics, very 40 | popular during the Renaissance. The first line of Lorem Ipsum, "Lorem 41 | ipsum dolor sit amet..", comes from a line in section 1.10.32. 42 | 43 | 44 | ); 45 | } 46 | } 47 | 48 | const styles = StyleSheet.create({ 49 | container: { 50 | backgroundColor: 'white', 51 | }, 52 | content: { 53 | paddingVertical: 16, 54 | }, 55 | author: { 56 | flexDirection: 'row', 57 | marginVertical: 8, 58 | marginHorizontal: 16, 59 | }, 60 | meta: { 61 | marginHorizontal: 8, 62 | justifyContent: 'center', 63 | }, 64 | name: { 65 | color: '#000', 66 | fontWeight: 'bold', 67 | fontSize: 16, 68 | lineHeight: 24, 69 | }, 70 | timestamp: { 71 | color: '#999', 72 | fontSize: 14, 73 | lineHeight: 21, 74 | }, 75 | avatar: { 76 | height: 48, 77 | width: 48, 78 | borderRadius: 24, 79 | }, 80 | title: { 81 | color: '#000', 82 | fontWeight: 'bold', 83 | fontSize: 36, 84 | marginVertical: 8, 85 | marginHorizontal: 16, 86 | }, 87 | paragraph: { 88 | color: '#000', 89 | fontSize: 16, 90 | lineHeight: 24, 91 | marginVertical: 8, 92 | marginHorizontal: 16, 93 | }, 94 | image: { 95 | width: '100%', 96 | height: 200, 97 | resizeMode: 'cover', 98 | marginVertical: 8, 99 | }, 100 | }); 101 | -------------------------------------------------------------------------------- /flow-typed/npm/@expo/vector-icons_vx.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: ab1d56d8498dc48b31c5bab4cf5573db 2 | // flow-typed version: <>/@expo/vector-icons_v^6.1.0/flow_v0.53.1 3 | 4 | /** 5 | * This is an autogenerated libdef stub for: 6 | * 7 | * '@expo/vector-icons' 8 | * 9 | * Fill this stub out by replacing all the `any` types. 10 | * 11 | * Once filled out, we encourage you to share your work with the 12 | * community by sending a pull request to: 13 | * https://github.com/flowtype/flow-typed 14 | */ 15 | 16 | declare module '@expo/vector-icons' { 17 | declare module.exports: any; 18 | } 19 | 20 | /** 21 | * We include stubs for each file inside this npm package in case you need to 22 | * require those files directly. Feel free to delete any files that aren't 23 | * needed. 24 | */ 25 | declare module '@expo/vector-icons/createIconSet' { 26 | declare module.exports: any; 27 | } 28 | 29 | declare module '@expo/vector-icons/createIconSetFromFontello' { 30 | declare module.exports: any; 31 | } 32 | 33 | declare module '@expo/vector-icons/createIconSetFromIcoMoon' { 34 | declare module.exports: any; 35 | } 36 | 37 | declare module '@expo/vector-icons/Entypo' { 38 | declare module.exports: any; 39 | } 40 | 41 | declare module '@expo/vector-icons/EvilIcons' { 42 | declare module.exports: any; 43 | } 44 | 45 | declare module '@expo/vector-icons/Feather' { 46 | declare module.exports: any; 47 | } 48 | 49 | declare module '@expo/vector-icons/FontAwesome' { 50 | declare module.exports: any; 51 | } 52 | 53 | declare module '@expo/vector-icons/Foundation' { 54 | declare module.exports: any; 55 | } 56 | 57 | declare module '@expo/vector-icons/iconFontSources' { 58 | declare module.exports: any; 59 | } 60 | 61 | declare module '@expo/vector-icons/Ionicons' { 62 | declare module.exports: any; 63 | } 64 | 65 | declare module '@expo/vector-icons/MaterialCommunityIcons' { 66 | declare module.exports: any; 67 | } 68 | 69 | declare module '@expo/vector-icons/MaterialIcons' { 70 | declare module.exports: any; 71 | } 72 | 73 | declare module '@expo/vector-icons/Octicons' { 74 | declare module.exports: any; 75 | } 76 | 77 | declare module '@expo/vector-icons/SimpleLineIcons' { 78 | declare module.exports: any; 79 | } 80 | 81 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/Entypo' { 82 | declare module.exports: any; 83 | } 84 | 85 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/EvilIcons' { 86 | declare module.exports: any; 87 | } 88 | 89 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/Feather' { 90 | declare module.exports: any; 91 | } 92 | 93 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/FontAwesome' { 94 | declare module.exports: any; 95 | } 96 | 97 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/Foundation' { 98 | declare module.exports: any; 99 | } 100 | 101 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/generate-icon' { 102 | declare module.exports: any; 103 | } 104 | 105 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/generate-material-icons' { 106 | declare module.exports: any; 107 | } 108 | 109 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/index' { 110 | declare module.exports: any; 111 | } 112 | 113 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/Ionicons' { 114 | declare module.exports: any; 115 | } 116 | 117 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/create-icon-set-from-fontello' { 118 | declare module.exports: any; 119 | } 120 | 121 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/create-icon-set-from-icomoon' { 122 | declare module.exports: any; 123 | } 124 | 125 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/create-icon-set' { 126 | declare module.exports: any; 127 | } 128 | 129 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/generate-icon-set-from-css' { 130 | declare module.exports: any; 131 | } 132 | 133 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/icon-button' { 134 | declare module.exports: any; 135 | } 136 | 137 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/react-native' { 138 | declare module.exports: any; 139 | } 140 | 141 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/react-native.osx' { 142 | declare module.exports: any; 143 | } 144 | 145 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/tab-bar-item-ios' { 146 | declare module.exports: any; 147 | } 148 | 149 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/toolbar-android' { 150 | declare module.exports: any; 151 | } 152 | 153 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/MaterialCommunityIcons' { 154 | declare module.exports: any; 155 | } 156 | 157 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/MaterialIcons' { 158 | declare module.exports: any; 159 | } 160 | 161 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/Octicons' { 162 | declare module.exports: any; 163 | } 164 | 165 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/RNIMigration' { 166 | declare module.exports: any; 167 | } 168 | 169 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/SimpleLineIcons' { 170 | declare module.exports: any; 171 | } 172 | 173 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/Zocial' { 174 | declare module.exports: any; 175 | } 176 | 177 | declare module '@expo/vector-icons/website/err-if-inside-universe' { 178 | declare module.exports: any; 179 | } 180 | 181 | declare module '@expo/vector-icons/website/src/App' { 182 | declare module.exports: any; 183 | } 184 | 185 | declare module '@expo/vector-icons/website/src/index' { 186 | declare module.exports: any; 187 | } 188 | 189 | declare module '@expo/vector-icons/Zocial' { 190 | declare module.exports: any; 191 | } 192 | 193 | // Filename aliases 194 | declare module '@expo/vector-icons/createIconSet.js' { 195 | declare module.exports: $Exports<'@expo/vector-icons/createIconSet'>; 196 | } 197 | declare module '@expo/vector-icons/createIconSetFromFontello.js' { 198 | declare module.exports: $Exports<'@expo/vector-icons/createIconSetFromFontello'>; 199 | } 200 | declare module '@expo/vector-icons/createIconSetFromIcoMoon.js' { 201 | declare module.exports: $Exports<'@expo/vector-icons/createIconSetFromIcoMoon'>; 202 | } 203 | declare module '@expo/vector-icons/Entypo.js' { 204 | declare module.exports: $Exports<'@expo/vector-icons/Entypo'>; 205 | } 206 | declare module '@expo/vector-icons/EvilIcons.js' { 207 | declare module.exports: $Exports<'@expo/vector-icons/EvilIcons'>; 208 | } 209 | declare module '@expo/vector-icons/Feather.js' { 210 | declare module.exports: $Exports<'@expo/vector-icons/Feather'>; 211 | } 212 | declare module '@expo/vector-icons/FontAwesome.js' { 213 | declare module.exports: $Exports<'@expo/vector-icons/FontAwesome'>; 214 | } 215 | declare module '@expo/vector-icons/Foundation.js' { 216 | declare module.exports: $Exports<'@expo/vector-icons/Foundation'>; 217 | } 218 | declare module '@expo/vector-icons/iconFontSources.js' { 219 | declare module.exports: $Exports<'@expo/vector-icons/iconFontSources'>; 220 | } 221 | declare module '@expo/vector-icons/index' { 222 | declare module.exports: $Exports<'@expo/vector-icons'>; 223 | } 224 | declare module '@expo/vector-icons/index.js' { 225 | declare module.exports: $Exports<'@expo/vector-icons'>; 226 | } 227 | declare module '@expo/vector-icons/Ionicons.js' { 228 | declare module.exports: $Exports<'@expo/vector-icons/Ionicons'>; 229 | } 230 | declare module '@expo/vector-icons/MaterialCommunityIcons.js' { 231 | declare module.exports: $Exports<'@expo/vector-icons/MaterialCommunityIcons'>; 232 | } 233 | declare module '@expo/vector-icons/MaterialIcons.js' { 234 | declare module.exports: $Exports<'@expo/vector-icons/MaterialIcons'>; 235 | } 236 | declare module '@expo/vector-icons/Octicons.js' { 237 | declare module.exports: $Exports<'@expo/vector-icons/Octicons'>; 238 | } 239 | declare module '@expo/vector-icons/SimpleLineIcons.js' { 240 | declare module.exports: $Exports<'@expo/vector-icons/SimpleLineIcons'>; 241 | } 242 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/Entypo.js' { 243 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/Entypo'>; 244 | } 245 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/EvilIcons.js' { 246 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/EvilIcons'>; 247 | } 248 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/Feather.js' { 249 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/Feather'>; 250 | } 251 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/FontAwesome.js' { 252 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/FontAwesome'>; 253 | } 254 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/Foundation.js' { 255 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/Foundation'>; 256 | } 257 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/generate-icon.js' { 258 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/generate-icon'>; 259 | } 260 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/generate-material-icons.js' { 261 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/generate-material-icons'>; 262 | } 263 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/index.js' { 264 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/index'>; 265 | } 266 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/Ionicons.js' { 267 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/Ionicons'>; 268 | } 269 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/create-icon-set-from-fontello.js' { 270 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/lib/create-icon-set-from-fontello'>; 271 | } 272 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/create-icon-set-from-icomoon.js' { 273 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/lib/create-icon-set-from-icomoon'>; 274 | } 275 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/create-icon-set.js' { 276 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/lib/create-icon-set'>; 277 | } 278 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/generate-icon-set-from-css.js' { 279 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/lib/generate-icon-set-from-css'>; 280 | } 281 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/icon-button.js' { 282 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/lib/icon-button'>; 283 | } 284 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/react-native.js' { 285 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/lib/react-native'>; 286 | } 287 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/react-native.osx.js' { 288 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/lib/react-native.osx'>; 289 | } 290 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/tab-bar-item-ios.js' { 291 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/lib/tab-bar-item-ios'>; 292 | } 293 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/lib/toolbar-android.js' { 294 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/lib/toolbar-android'>; 295 | } 296 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/MaterialCommunityIcons.js' { 297 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/MaterialCommunityIcons'>; 298 | } 299 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/MaterialIcons.js' { 300 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/MaterialIcons'>; 301 | } 302 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/Octicons.js' { 303 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/Octicons'>; 304 | } 305 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/RNIMigration.js' { 306 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/RNIMigration'>; 307 | } 308 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/SimpleLineIcons.js' { 309 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/SimpleLineIcons'>; 310 | } 311 | declare module '@expo/vector-icons/vendor/react-native-vector-icons/Zocial.js' { 312 | declare module.exports: $Exports<'@expo/vector-icons/vendor/react-native-vector-icons/Zocial'>; 313 | } 314 | declare module '@expo/vector-icons/website/err-if-inside-universe.js' { 315 | declare module.exports: $Exports<'@expo/vector-icons/website/err-if-inside-universe'>; 316 | } 317 | declare module '@expo/vector-icons/website/src/App.js' { 318 | declare module.exports: $Exports<'@expo/vector-icons/website/src/App'>; 319 | } 320 | declare module '@expo/vector-icons/website/src/index.js' { 321 | declare module.exports: $Exports<'@expo/vector-icons/website/src/index'>; 322 | } 323 | declare module '@expo/vector-icons/Zocial.js' { 324 | declare module.exports: $Exports<'@expo/vector-icons/Zocial'>; 325 | } 326 | -------------------------------------------------------------------------------- /flow-typed/npm/enzyme_v3.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: a18e8395a43c22fe55906624f2a7ddb9 2 | // flow-typed version: e351e417db/enzyme_v3.x.x/flow_>=v0.53.x 3 | 4 | import * as React from "react"; 5 | 6 | declare module "enzyme" { 7 | declare type PredicateFunction = ( 8 | wrapper: T, 9 | index: number 10 | ) => boolean; 11 | declare type NodeOrNodes = React.Node | Array; 12 | declare type EnzymeSelector = string | Class> | Object; 13 | 14 | // CheerioWrapper is a type alias for an actual cheerio instance 15 | // TODO: Reference correct type from cheerio's type declarations 16 | declare type CheerioWrapper = any; 17 | 18 | declare class Wrapper { 19 | find(selector: EnzymeSelector): this, 20 | findWhere(predicate: PredicateFunction): this, 21 | filter(selector: EnzymeSelector): this, 22 | filterWhere(predicate: PredicateFunction): this, 23 | contains(nodeOrNodes: NodeOrNodes): boolean, 24 | containsMatchingElement(node: React.Node): boolean, 25 | containsAllMatchingElements(nodes: NodeOrNodes): boolean, 26 | containsAnyMatchingElements(nodes: NodeOrNodes): boolean, 27 | dive(option?: { context?: Object }): this, 28 | exists(): boolean, 29 | matchesElement(node: React.Node): boolean, 30 | hasClass(className: string): boolean, 31 | is(selector: EnzymeSelector): boolean, 32 | isEmpty(): boolean, 33 | not(selector: EnzymeSelector): this, 34 | children(selector?: EnzymeSelector): this, 35 | childAt(index: number): this, 36 | parents(selector?: EnzymeSelector): this, 37 | parent(): this, 38 | closest(selector: EnzymeSelector): this, 39 | render(): CheerioWrapper, 40 | unmount(): this, 41 | text(): string, 42 | html(): string, 43 | get(index: number): React.Node, 44 | getNodes(): Array, 45 | getDOMNode(): HTMLElement | HTMLInputElement, 46 | at(index: number): this, 47 | first(): this, 48 | last(): this, 49 | state(key?: string): any, 50 | context(key?: string): any, 51 | props(): Object, 52 | prop(key: string): any, 53 | key(): string, 54 | simulate(event: string, ...args: Array): this, 55 | setState(state: {}, callback?: Function): this, 56 | setProps(props: {}): this, 57 | setContext(context: Object): this, 58 | instance(): React.Component<*, *>, 59 | update(): this, 60 | debug(): string, 61 | type(): string | Function | null, 62 | name(): string, 63 | forEach(fn: (node: this, index: number) => mixed): this, 64 | map(fn: (node: this, index: number) => T): Array, 65 | reduce( 66 | fn: (value: T, node: this, index: number) => T, 67 | initialValue?: T 68 | ): Array, 69 | reduceRight( 70 | fn: (value: T, node: this, index: number) => T, 71 | initialValue?: T 72 | ): Array, 73 | some(selector: EnzymeSelector): boolean, 74 | someWhere(predicate: PredicateFunction): boolean, 75 | every(selector: EnzymeSelector): boolean, 76 | everyWhere(predicate: PredicateFunction): boolean, 77 | length: number 78 | } 79 | 80 | declare class ReactWrapper extends Wrapper { 81 | constructor(nodes: NodeOrNodes, root: any, options?: ?Object): ReactWrapper, 82 | mount(): this, 83 | ref(refName: string): this, 84 | detach(): void 85 | } 86 | 87 | declare class ShallowWrapper extends Wrapper { 88 | constructor( 89 | nodes: NodeOrNodes, 90 | root: any, 91 | options?: ?Object 92 | ): ShallowWrapper, 93 | equals(node: React.Node): boolean, 94 | shallow(options?: { context?: Object }): ShallowWrapper 95 | } 96 | 97 | declare function shallow( 98 | node: React.Node, 99 | options?: { context?: Object, disableLifecycleMethods?: boolean } 100 | ): ShallowWrapper; 101 | declare function mount( 102 | node: React.Node, 103 | options?: { 104 | context?: Object, 105 | attachTo?: HTMLElement, 106 | childContextTypes?: Object 107 | } 108 | ): ReactWrapper; 109 | declare function render( 110 | node: React.Node, 111 | options?: { context?: Object } 112 | ): CheerioWrapper; 113 | 114 | declare module.exports: { 115 | configure(options: { 116 | Adapter?: any, 117 | disableLifecycleMethods?: boolean 118 | }): void, 119 | render: typeof render, 120 | mount: typeof mount, 121 | shallow: typeof shallow, 122 | ShallowWrapper: typeof ShallowWrapper, 123 | ReactWrapper: typeof ReactWrapper 124 | }; 125 | } 126 | -------------------------------------------------------------------------------- /flow-typed/npm/flow-bin_v0.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 6a5610678d4b01e13bbfbbc62bdaf583 2 | // flow-typed version: 3817bc6980/flow-bin_v0.x.x/flow_>=v0.25.x 3 | 4 | declare module "flow-bin" { 5 | declare module.exports: string; 6 | } 7 | -------------------------------------------------------------------------------- /flow-typed/npm/jest_v21.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 3df9907bbe7831a5c2c03eb838f6b3c0 2 | // flow-typed version: 0fd239bfe0/jest_v21.x.x/flow_>=v0.39.x 3 | 4 | type JestMockFn, TReturn> = { 5 | (...args: TArguments): TReturn, 6 | /** 7 | * An object for introspecting mock calls 8 | */ 9 | mock: { 10 | /** 11 | * An array that represents all calls that have been made into this mock 12 | * function. Each call is represented by an array of arguments that were 13 | * passed during the call. 14 | */ 15 | calls: Array, 16 | /** 17 | * An array that contains all the object instances that have been 18 | * instantiated from this mock function. 19 | */ 20 | instances: Array 21 | }, 22 | /** 23 | * Resets all information stored in the mockFn.mock.calls and 24 | * mockFn.mock.instances arrays. Often this is useful when you want to clean 25 | * up a mock's usage data between two assertions. 26 | */ 27 | mockClear(): void, 28 | /** 29 | * Resets all information stored in the mock. This is useful when you want to 30 | * completely restore a mock back to its initial state. 31 | */ 32 | mockReset(): void, 33 | /** 34 | * Removes the mock and restores the initial implementation. This is useful 35 | * when you want to mock functions in certain test cases and restore the 36 | * original implementation in others. Beware that mockFn.mockRestore only 37 | * works when mock was created with jest.spyOn. Thus you have to take care of 38 | * restoration yourself when manually assigning jest.fn(). 39 | */ 40 | mockRestore(): void, 41 | /** 42 | * Accepts a function that should be used as the implementation of the mock. 43 | * The mock itself will still record all calls that go into and instances 44 | * that come from itself -- the only difference is that the implementation 45 | * will also be executed when the mock is called. 46 | */ 47 | mockImplementation( 48 | fn: (...args: TArguments) => TReturn 49 | ): JestMockFn, 50 | /** 51 | * Accepts a function that will be used as an implementation of the mock for 52 | * one call to the mocked function. Can be chained so that multiple function 53 | * calls produce different results. 54 | */ 55 | mockImplementationOnce( 56 | fn: (...args: TArguments) => TReturn 57 | ): JestMockFn, 58 | /** 59 | * Just a simple sugar function for returning `this` 60 | */ 61 | mockReturnThis(): void, 62 | /** 63 | * Deprecated: use jest.fn(() => value) instead 64 | */ 65 | mockReturnValue(value: TReturn): JestMockFn, 66 | /** 67 | * Sugar for only returning a value once inside your mock 68 | */ 69 | mockReturnValueOnce(value: TReturn): JestMockFn 70 | }; 71 | 72 | type JestAsymmetricEqualityType = { 73 | /** 74 | * A custom Jasmine equality tester 75 | */ 76 | asymmetricMatch(value: mixed): boolean 77 | }; 78 | 79 | type JestCallsType = { 80 | allArgs(): mixed, 81 | all(): mixed, 82 | any(): boolean, 83 | count(): number, 84 | first(): mixed, 85 | mostRecent(): mixed, 86 | reset(): void 87 | }; 88 | 89 | type JestClockType = { 90 | install(): void, 91 | mockDate(date: Date): void, 92 | tick(milliseconds?: number): void, 93 | uninstall(): void 94 | }; 95 | 96 | type JestMatcherResult = { 97 | message?: string | (() => string), 98 | pass: boolean 99 | }; 100 | 101 | type JestMatcher = (actual: any, expected: any) => JestMatcherResult; 102 | 103 | type JestPromiseType = { 104 | /** 105 | * Use rejects to unwrap the reason of a rejected promise so any other 106 | * matcher can be chained. If the promise is fulfilled the assertion fails. 107 | */ 108 | rejects: JestExpectType, 109 | /** 110 | * Use resolves to unwrap the value of a fulfilled promise so any other 111 | * matcher can be chained. If the promise is rejected the assertion fails. 112 | */ 113 | resolves: JestExpectType 114 | }; 115 | 116 | /** 117 | * Plugin: jest-enzyme 118 | */ 119 | type EnzymeMatchersType = { 120 | toBeChecked(): void, 121 | toBeDisabled(): void, 122 | toBeEmpty(): void, 123 | toBePresent(): void, 124 | toContainReact(element: React$Element): void, 125 | toHaveClassName(className: string): void, 126 | toHaveHTML(html: string): void, 127 | toHaveProp(propKey: string, propValue?: any): void, 128 | toHaveRef(refName: string): void, 129 | toHaveState(stateKey: string, stateValue?: any): void, 130 | toHaveStyle(styleKey: string, styleValue?: any): void, 131 | toHaveTagName(tagName: string): void, 132 | toHaveText(text: string): void, 133 | toIncludeText(text: string): void, 134 | toHaveValue(value: any): void, 135 | toMatchElement(element: React$Element): void, 136 | toMatchSelector(selector: string): void 137 | }; 138 | 139 | type JestExpectType = { 140 | not: JestExpectType & EnzymeMatchersType, 141 | /** 142 | * If you have a mock function, you can use .lastCalledWith to test what 143 | * arguments it was last called with. 144 | */ 145 | lastCalledWith(...args: Array): void, 146 | /** 147 | * toBe just checks that a value is what you expect. It uses === to check 148 | * strict equality. 149 | */ 150 | toBe(value: any): void, 151 | /** 152 | * Use .toHaveBeenCalled to ensure that a mock function got called. 153 | */ 154 | toBeCalled(): void, 155 | /** 156 | * Use .toBeCalledWith to ensure that a mock function was called with 157 | * specific arguments. 158 | */ 159 | toBeCalledWith(...args: Array): void, 160 | /** 161 | * Using exact equality with floating point numbers is a bad idea. Rounding 162 | * means that intuitive things fail. 163 | */ 164 | toBeCloseTo(num: number, delta: any): void, 165 | /** 166 | * Use .toBeDefined to check that a variable is not undefined. 167 | */ 168 | toBeDefined(): void, 169 | /** 170 | * Use .toBeFalsy when you don't care what a value is, you just want to 171 | * ensure a value is false in a boolean context. 172 | */ 173 | toBeFalsy(): void, 174 | /** 175 | * To compare floating point numbers, you can use toBeGreaterThan. 176 | */ 177 | toBeGreaterThan(number: number): void, 178 | /** 179 | * To compare floating point numbers, you can use toBeGreaterThanOrEqual. 180 | */ 181 | toBeGreaterThanOrEqual(number: number): void, 182 | /** 183 | * To compare floating point numbers, you can use toBeLessThan. 184 | */ 185 | toBeLessThan(number: number): void, 186 | /** 187 | * To compare floating point numbers, you can use toBeLessThanOrEqual. 188 | */ 189 | toBeLessThanOrEqual(number: number): void, 190 | /** 191 | * Use .toBeInstanceOf(Class) to check that an object is an instance of a 192 | * class. 193 | */ 194 | toBeInstanceOf(cls: Class<*>): void, 195 | /** 196 | * .toBeNull() is the same as .toBe(null) but the error messages are a bit 197 | * nicer. 198 | */ 199 | toBeNull(): void, 200 | /** 201 | * Use .toBeTruthy when you don't care what a value is, you just want to 202 | * ensure a value is true in a boolean context. 203 | */ 204 | toBeTruthy(): void, 205 | /** 206 | * Use .toBeUndefined to check that a variable is undefined. 207 | */ 208 | toBeUndefined(): void, 209 | /** 210 | * Use .toContain when you want to check that an item is in a list. For 211 | * testing the items in the list, this uses ===, a strict equality check. 212 | */ 213 | toContain(item: any): void, 214 | /** 215 | * Use .toContainEqual when you want to check that an item is in a list. For 216 | * testing the items in the list, this matcher recursively checks the 217 | * equality of all fields, rather than checking for object identity. 218 | */ 219 | toContainEqual(item: any): void, 220 | /** 221 | * Use .toEqual when you want to check that two objects have the same value. 222 | * This matcher recursively checks the equality of all fields, rather than 223 | * checking for object identity. 224 | */ 225 | toEqual(value: any): void, 226 | /** 227 | * Use .toHaveBeenCalled to ensure that a mock function got called. 228 | */ 229 | toHaveBeenCalled(): void, 230 | /** 231 | * Use .toHaveBeenCalledTimes to ensure that a mock function got called exact 232 | * number of times. 233 | */ 234 | toHaveBeenCalledTimes(number: number): void, 235 | /** 236 | * Use .toHaveBeenCalledWith to ensure that a mock function was called with 237 | * specific arguments. 238 | */ 239 | toHaveBeenCalledWith(...args: Array): void, 240 | /** 241 | * Use .toHaveBeenLastCalledWith to ensure that a mock function was last called 242 | * with specific arguments. 243 | */ 244 | toHaveBeenLastCalledWith(...args: Array): void, 245 | /** 246 | * Check that an object has a .length property and it is set to a certain 247 | * numeric value. 248 | */ 249 | toHaveLength(number: number): void, 250 | /** 251 | * 252 | */ 253 | toHaveProperty(propPath: string, value?: any): void, 254 | /** 255 | * Use .toMatch to check that a string matches a regular expression or string. 256 | */ 257 | toMatch(regexpOrString: RegExp | string): void, 258 | /** 259 | * Use .toMatchObject to check that a javascript object matches a subset of the properties of an object. 260 | */ 261 | toMatchObject(object: Object): void, 262 | /** 263 | * This ensures that a React component matches the most recent snapshot. 264 | */ 265 | toMatchSnapshot(name?: string): void, 266 | /** 267 | * Use .toThrow to test that a function throws when it is called. 268 | * If you want to test that a specific error gets thrown, you can provide an 269 | * argument to toThrow. The argument can be a string for the error message, 270 | * a class for the error, or a regex that should match the error. 271 | * 272 | * Alias: .toThrowError 273 | */ 274 | toThrow(message?: string | Error | RegExp): void, 275 | toThrowError(message?: string | Error | RegExp): void, 276 | /** 277 | * Use .toThrowErrorMatchingSnapshot to test that a function throws a error 278 | * matching the most recent snapshot when it is called. 279 | */ 280 | toThrowErrorMatchingSnapshot(): void 281 | }; 282 | 283 | type JestObjectType = { 284 | /** 285 | * Disables automatic mocking in the module loader. 286 | * 287 | * After this method is called, all `require()`s will return the real 288 | * versions of each module (rather than a mocked version). 289 | */ 290 | disableAutomock(): JestObjectType, 291 | /** 292 | * An un-hoisted version of disableAutomock 293 | */ 294 | autoMockOff(): JestObjectType, 295 | /** 296 | * Enables automatic mocking in the module loader. 297 | */ 298 | enableAutomock(): JestObjectType, 299 | /** 300 | * An un-hoisted version of enableAutomock 301 | */ 302 | autoMockOn(): JestObjectType, 303 | /** 304 | * Clears the mock.calls and mock.instances properties of all mocks. 305 | * Equivalent to calling .mockClear() on every mocked function. 306 | */ 307 | clearAllMocks(): JestObjectType, 308 | /** 309 | * Resets the state of all mocks. Equivalent to calling .mockReset() on every 310 | * mocked function. 311 | */ 312 | resetAllMocks(): JestObjectType, 313 | /** 314 | * Removes any pending timers from the timer system. 315 | */ 316 | clearAllTimers(): void, 317 | /** 318 | * The same as `mock` but not moved to the top of the expectation by 319 | * babel-jest. 320 | */ 321 | doMock(moduleName: string, moduleFactory?: any): JestObjectType, 322 | /** 323 | * The same as `unmock` but not moved to the top of the expectation by 324 | * babel-jest. 325 | */ 326 | dontMock(moduleName: string): JestObjectType, 327 | /** 328 | * Returns a new, unused mock function. Optionally takes a mock 329 | * implementation. 330 | */ 331 | fn, TReturn>( 332 | implementation?: (...args: TArguments) => TReturn 333 | ): JestMockFn, 334 | /** 335 | * Determines if the given function is a mocked function. 336 | */ 337 | isMockFunction(fn: Function): boolean, 338 | /** 339 | * Given the name of a module, use the automatic mocking system to generate a 340 | * mocked version of the module for you. 341 | */ 342 | genMockFromModule(moduleName: string): any, 343 | /** 344 | * Mocks a module with an auto-mocked version when it is being required. 345 | * 346 | * The second argument can be used to specify an explicit module factory that 347 | * is being run instead of using Jest's automocking feature. 348 | * 349 | * The third argument can be used to create virtual mocks -- mocks of modules 350 | * that don't exist anywhere in the system. 351 | */ 352 | mock( 353 | moduleName: string, 354 | moduleFactory?: any, 355 | options?: Object 356 | ): JestObjectType, 357 | /** 358 | * Returns the actual module instead of a mock, bypassing all checks on 359 | * whether the module should receive a mock implementation or not. 360 | */ 361 | requireActual(moduleName: string): any, 362 | /** 363 | * Returns a mock module instead of the actual module, bypassing all checks 364 | * on whether the module should be required normally or not. 365 | */ 366 | requireMock(moduleName: string): any, 367 | /** 368 | * Resets the module registry - the cache of all required modules. This is 369 | * useful to isolate modules where local state might conflict between tests. 370 | */ 371 | resetModules(): JestObjectType, 372 | /** 373 | * Exhausts the micro-task queue (usually interfaced in node via 374 | * process.nextTick). 375 | */ 376 | runAllTicks(): void, 377 | /** 378 | * Exhausts the macro-task queue (i.e., all tasks queued by setTimeout(), 379 | * setInterval(), and setImmediate()). 380 | */ 381 | runAllTimers(): void, 382 | /** 383 | * Exhausts all tasks queued by setImmediate(). 384 | */ 385 | runAllImmediates(): void, 386 | /** 387 | * Executes only the macro task queue (i.e. all tasks queued by setTimeout() 388 | * or setInterval() and setImmediate()). 389 | */ 390 | runTimersToTime(msToRun: number): void, 391 | /** 392 | * Executes only the macro-tasks that are currently pending (i.e., only the 393 | * tasks that have been queued by setTimeout() or setInterval() up to this 394 | * point) 395 | */ 396 | runOnlyPendingTimers(): void, 397 | /** 398 | * Explicitly supplies the mock object that the module system should return 399 | * for the specified module. Note: It is recommended to use jest.mock() 400 | * instead. 401 | */ 402 | setMock(moduleName: string, moduleExports: any): JestObjectType, 403 | /** 404 | * Indicates that the module system should never return a mocked version of 405 | * the specified module from require() (e.g. that it should always return the 406 | * real module). 407 | */ 408 | unmock(moduleName: string): JestObjectType, 409 | /** 410 | * Instructs Jest to use fake versions of the standard timer functions 411 | * (setTimeout, setInterval, clearTimeout, clearInterval, nextTick, 412 | * setImmediate and clearImmediate). 413 | */ 414 | useFakeTimers(): JestObjectType, 415 | /** 416 | * Instructs Jest to use the real versions of the standard timer functions. 417 | */ 418 | useRealTimers(): JestObjectType, 419 | /** 420 | * Creates a mock function similar to jest.fn but also tracks calls to 421 | * object[methodName]. 422 | */ 423 | spyOn(object: Object, methodName: string): JestMockFn 424 | }; 425 | 426 | type JestSpyType = { 427 | calls: JestCallsType 428 | }; 429 | 430 | /** Runs this function after every test inside this context */ 431 | declare function afterEach( 432 | fn: (done: () => void) => ?Promise, 433 | timeout?: number 434 | ): void; 435 | /** Runs this function before every test inside this context */ 436 | declare function beforeEach( 437 | fn: (done: () => void) => ?Promise, 438 | timeout?: number 439 | ): void; 440 | /** Runs this function after all tests have finished inside this context */ 441 | declare function afterAll( 442 | fn: (done: () => void) => ?Promise, 443 | timeout?: number 444 | ): void; 445 | /** Runs this function before any tests have started inside this context */ 446 | declare function beforeAll( 447 | fn: (done: () => void) => ?Promise, 448 | timeout?: number 449 | ): void; 450 | 451 | /** A context for grouping tests together */ 452 | declare var describe: { 453 | /** 454 | * Creates a block that groups together several related tests in one "test suite" 455 | */ 456 | (name: string, fn: () => void): void, 457 | 458 | /** 459 | * Only run this describe block 460 | */ 461 | only(name: string, fn: () => void): void, 462 | 463 | /** 464 | * Skip running this describe block 465 | */ 466 | skip(name: string, fn: () => void): void 467 | }; 468 | 469 | /** An individual test unit */ 470 | declare var it: { 471 | /** 472 | * An individual test unit 473 | * 474 | * @param {string} Name of Test 475 | * @param {Function} Test 476 | * @param {number} Timeout for the test, in milliseconds. 477 | */ 478 | ( 479 | name: string, 480 | fn?: (done: () => void) => ?Promise, 481 | timeout?: number 482 | ): void, 483 | /** 484 | * Only run this test 485 | * 486 | * @param {string} Name of Test 487 | * @param {Function} Test 488 | * @param {number} Timeout for the test, in milliseconds. 489 | */ 490 | only( 491 | name: string, 492 | fn?: (done: () => void) => ?Promise, 493 | timeout?: number 494 | ): void, 495 | /** 496 | * Skip running this test 497 | * 498 | * @param {string} Name of Test 499 | * @param {Function} Test 500 | * @param {number} Timeout for the test, in milliseconds. 501 | */ 502 | skip( 503 | name: string, 504 | fn?: (done: () => void) => ?Promise, 505 | timeout?: number 506 | ): void, 507 | /** 508 | * Run the test concurrently 509 | * 510 | * @param {string} Name of Test 511 | * @param {Function} Test 512 | * @param {number} Timeout for the test, in milliseconds. 513 | */ 514 | concurrent( 515 | name: string, 516 | fn?: (done: () => void) => ?Promise, 517 | timeout?: number 518 | ): void 519 | }; 520 | declare function fit( 521 | name: string, 522 | fn: (done: () => void) => ?Promise, 523 | timeout?: number 524 | ): void; 525 | /** An individual test unit */ 526 | declare var test: typeof it; 527 | /** A disabled group of tests */ 528 | declare var xdescribe: typeof describe; 529 | /** A focused group of tests */ 530 | declare var fdescribe: typeof describe; 531 | /** A disabled individual test */ 532 | declare var xit: typeof it; 533 | /** A disabled individual test */ 534 | declare var xtest: typeof it; 535 | 536 | /** The expect function is used every time you want to test a value */ 537 | declare var expect: { 538 | /** The object that you want to make assertions against */ 539 | (value: any): JestExpectType & JestPromiseType & EnzymeMatchersType, 540 | /** Add additional Jasmine matchers to Jest's roster */ 541 | extend(matchers: { [name: string]: JestMatcher }): void, 542 | /** Add a module that formats application-specific data structures. */ 543 | addSnapshotSerializer(serializer: (input: Object) => string): void, 544 | assertions(expectedAssertions: number): void, 545 | hasAssertions(): void, 546 | any(value: mixed): JestAsymmetricEqualityType, 547 | anything(): void, 548 | arrayContaining(value: Array): void, 549 | objectContaining(value: Object): void, 550 | /** Matches any received string that contains the exact expected string. */ 551 | stringContaining(value: string): void, 552 | stringMatching(value: string | RegExp): void 553 | }; 554 | 555 | // TODO handle return type 556 | // http://jasmine.github.io/2.4/introduction.html#section-Spies 557 | declare function spyOn(value: mixed, method: string): Object; 558 | 559 | /** Holds all functions related to manipulating test runner */ 560 | declare var jest: JestObjectType; 561 | 562 | /** 563 | * The global Jamine object, this is generally not exposed as the public API, 564 | * using features inside here could break in later versions of Jest. 565 | */ 566 | declare var jasmine: { 567 | DEFAULT_TIMEOUT_INTERVAL: number, 568 | any(value: mixed): JestAsymmetricEqualityType, 569 | anything(): void, 570 | arrayContaining(value: Array): void, 571 | clock(): JestClockType, 572 | createSpy(name: string): JestSpyType, 573 | createSpyObj( 574 | baseName: string, 575 | methodNames: Array 576 | ): { [methodName: string]: JestSpyType }, 577 | objectContaining(value: Object): void, 578 | stringMatching(value: string): void 579 | }; 580 | -------------------------------------------------------------------------------- /flow-typed/npm/prop-types_v15.x.x.js: -------------------------------------------------------------------------------- 1 | // flow-typed signature: 3eaa1f24c7397b78a7481992d2cddcb2 2 | // flow-typed version: a1a20d4928/prop-types_v15.x.x/flow_>=v0.41.x 3 | 4 | type $npm$propTypes$ReactPropsCheckType = ( 5 | props: any, 6 | propName: string, 7 | componentName: string, 8 | href?: string) => ?Error; 9 | 10 | declare module 'prop-types' { 11 | declare var array: React$PropType$Primitive>; 12 | declare var bool: React$PropType$Primitive; 13 | declare var func: React$PropType$Primitive; 14 | declare var number: React$PropType$Primitive; 15 | declare var object: React$PropType$Primitive; 16 | declare var string: React$PropType$Primitive; 17 | declare var any: React$PropType$Primitive; 18 | declare var arrayOf: React$PropType$ArrayOf; 19 | declare var element: React$PropType$Primitive; /* TODO */ 20 | declare var instanceOf: React$PropType$InstanceOf; 21 | declare var node: React$PropType$Primitive; /* TODO */ 22 | declare var objectOf: React$PropType$ObjectOf; 23 | declare var oneOf: React$PropType$OneOf; 24 | declare var oneOfType: React$PropType$OneOfType; 25 | declare var shape: React$PropType$Shape; 26 | 27 | declare function checkPropTypes( 28 | propTypes: $Subtype<{[_: $Keys]: $npm$propTypes$ReactPropsCheckType}>, 29 | values: V, 30 | location: string, 31 | componentName: string, 32 | getStack: ?(() => ?string) 33 | ) : void; 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-vertical-tab-view", 3 | "version": "0.1.3", 4 | "description": "Vertical & Horizontal Tab view component for React Native", 5 | "main": "src/index.js", 6 | "files": [ 7 | "src/" 8 | ], 9 | "scripts": { 10 | "test": "jest", 11 | "flow": "flow", 12 | "lint": "eslint .", 13 | "precommit": "yarn lint && yarn flow && yarn test" 14 | }, 15 | "keywords": [ 16 | "react-native-component", 17 | "react-component", 18 | "react-native", 19 | "ios", 20 | "android", 21 | "tab", 22 | "swipe", 23 | "scrollable", 24 | "coverflow" 25 | ], 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/DaKaZ/react-native-vertical-tab-view.git" 29 | }, 30 | "author": "Michael S. Kazmier (https://github.com/DaKaZ/)", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/DaKaZ/react-native-vertical-tab-view/issues" 34 | }, 35 | "homepage": "https://github.com/DaKaZ/react-native-vertical-tab-view.git#readme", 36 | "dependencies": { 37 | "react-native-tab-view": "^1.0.2" 38 | }, 39 | "devDependencies": { 40 | "@expo/vector-icons": "^6.3.1", 41 | "babel-jest": "^22.4.4", 42 | "babel-preset-react-native": "^4.0.0", 43 | "enzyme": "3.3.0", 44 | "enzyme-adapter-react-16": "^1.1.1", 45 | "enzyme-to-json": "^3.3.4", 46 | "eslint": "^4.19.1", 47 | "eslint-config-satya164": "^1.0.2", 48 | "eslint-plugin-react-native-globals": "^0.1.2", 49 | "flow-bin": "~0.67.1", 50 | "husky": "^0.14.3", 51 | "jest": "^22.4.4", 52 | "prettier": "^1.13.4", 53 | "react": "16.3.1", 54 | "react-dom": "16.3.1", 55 | "react-native": "~0.55.4", 56 | "react-test-renderer": "16.3.1" 57 | }, 58 | "peerDependencies": { 59 | "react": "*", 60 | "react-native": "*" 61 | }, 62 | "jest": { 63 | "preset": "react-native", 64 | "setupFiles": [ 65 | "/__setup__/enzyme.js" 66 | ], 67 | "modulePathIgnorePatterns": [ 68 | "/example/node_modules" 69 | ], 70 | "snapshotSerializers": [ 71 | "enzyme-to-json/serializer" 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/TabBarVertical.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { 5 | Animated, 6 | NativeModules, 7 | StyleSheet, 8 | View, 9 | ScrollView, 10 | Platform, 11 | I18nManager, 12 | } from 'react-native'; 13 | import TouchableItem from './TouchableItem'; 14 | import type { Scene, SceneRendererProps } from './index'; 15 | import type { 16 | ViewStyleProp, 17 | TextStyleProp, 18 | } from 'react-native/Libraries/StyleSheet/StyleSheet'; 19 | 20 | type IndicatorProps = SceneRendererProps & { 21 | width: number, 22 | height: number, 23 | }; 24 | 25 | type Props = SceneRendererProps & { 26 | scrollEnabled?: boolean, 27 | bounces?: boolean, 28 | pressColor?: string, 29 | pressOpacity?: number, 30 | getLabelText: (scene: Scene) => ?string, 31 | getAccessible: (scene: Scene) => ?boolean, 32 | getAccessibilityLabel: (scene: Scene) => ?string, 33 | getTestID: (scene: Scene) => ?string, 34 | renderLabel?: (scene: Scene) => React.Node, 35 | renderIcon?: (scene: Scene) => React.Node, 36 | renderBadge?: (scene: Scene) => React.Node, 37 | renderIndicator?: (props: IndicatorProps) => React.Node, 38 | onTabPress?: (scene: Scene) => mixed, 39 | tabStyle?: ViewStyleProp, // Be sure to include height! 40 | indicatorStyle?: ViewStyleProp, 41 | labelStyle?: TextStyleProp, 42 | style?: ViewStyleProp, 43 | }; 44 | 45 | type State = {| 46 | visibility: Animated.Value, 47 | scrollAmount: Animated.Value, 48 | initialOffset: ?{| x: number, y: number |}, 49 | |}; 50 | 51 | const useNativeDriver = Boolean(NativeModules.NativeAnimatedModule); 52 | 53 | export default class TabBarVertical extends React.Component< 54 | Props, 55 | State 56 | > { 57 | static defaultProps = { 58 | getLabelText: ({ route }: Scene) => 59 | typeof route.title === 'string' ? route.title.toUpperCase() : route.title, 60 | getAccessible: ({ route }: Scene) => 61 | typeof route.accessible !== 'undefined' ? route.accessible : true, 62 | getAccessibilityLabel: ({ route }: Scene) => route.accessibilityLabel, 63 | getTestID: ({ route }: Scene) => route.testID, 64 | }; 65 | 66 | constructor(props: Props) { 67 | super(props); 68 | 69 | let initialVisibility = 1; 70 | 71 | if (this.props.scrollEnabled) { 72 | const tabHeight = this._getTabHeight(this.props); 73 | if (!tabHeight) { 74 | initialVisibility = 0; 75 | } 76 | } 77 | const initialOffset = 78 | props.scrollEnabled && props.layout.width 79 | ? { 80 | y: this._getScrollAmount(props, props.navigationState.index), 81 | x: 0, 82 | } 83 | : undefined; 84 | 85 | this.state = { 86 | visibility: new Animated.Value(initialVisibility), 87 | scrollAmount: new Animated.Value(0), 88 | initialOffset, 89 | }; 90 | } 91 | 92 | componentDidMount() { 93 | this.props.scrollEnabled && this._startTrackingPosition(); 94 | } 95 | 96 | componentDidUpdate(prevProps: Props) { 97 | const prevTabWidth = this._getTabWidth(prevProps); 98 | const currentTabWidth = this._getTabWidth(this.props); 99 | const pendingIndex = 100 | typeof this._pendingIndex === 'number' 101 | ? this._pendingIndex 102 | : this.props.navigationState.index; 103 | 104 | this._pendingIndex = null; 105 | 106 | if (prevTabWidth !== currentTabWidth && currentTabWidth) { 107 | this.state.visibility.setValue(1); 108 | } 109 | 110 | if ( 111 | prevProps.navigationState.routes.length !== 112 | this.props.navigationState.routes.length || 113 | prevProps.layout.width !== this.props.layout.width 114 | ) { 115 | this._resetScroll(this.props.navigationState.index, false); 116 | } else if (prevProps.navigationState.index !== pendingIndex) { 117 | this._resetScroll(this.props.navigationState.index); 118 | } 119 | } 120 | 121 | componentWillUnmount() { 122 | this._stopTrackingPosition(); 123 | } 124 | 125 | _scrollView: ?ScrollView; 126 | _isIntial: boolean = true; 127 | _isManualScroll: boolean = false; 128 | _isMomentumScroll: boolean = false; 129 | _pendingIndex: ?number; 130 | _scrollResetCallback: any; 131 | _lastPanX: ?number; 132 | _lastOffsetX: ?number; 133 | _panXListener: string; 134 | _offsetXListener: string; 135 | 136 | _startTrackingPosition = () => { 137 | this._offsetXListener = this.props.offsetX.addListener(({ value }) => { 138 | this._lastOffsetX = value; 139 | this._handlePosition(); 140 | }); 141 | this._panXListener = this.props.panX.addListener(({ value }) => { 142 | this._lastPanX = value; 143 | this._handlePosition(); 144 | }); 145 | }; 146 | 147 | _stopTrackingPosition = () => { 148 | this.props.offsetX.removeListener(this._offsetXListener); 149 | this.props.panX.removeListener(this._panXListener); 150 | }; 151 | 152 | _handlePosition = () => { 153 | const { navigationState, layout } = this.props; 154 | 155 | if (layout.width === 0) { 156 | // Don't do anything if we don't have layout yet 157 | return; 158 | } 159 | 160 | const panX = typeof this._lastPanX === 'number' ? this._lastPanX : 0; 161 | const offsetX = 162 | typeof this._lastOffsetX === 'number' 163 | ? this._lastOffsetX 164 | : -navigationState.index * layout.width; 165 | 166 | const value = (panX + offsetX) / -(layout.width || 0.001); 167 | 168 | this._adjustScroll(value); 169 | }; 170 | 171 | _renderLabel = (scene: Scene<*>) => { 172 | if (typeof this.props.renderLabel !== 'undefined') { 173 | return this.props.renderLabel(scene); 174 | } 175 | const label = this.props.getLabelText(scene); 176 | if (typeof label !== 'string') { 177 | return null; 178 | } 179 | return ( 180 | 181 | {label} 182 | 183 | ); 184 | }; 185 | 186 | _renderIndicator = (props: IndicatorProps) => { 187 | if (typeof this.props.renderIndicator !== 'undefined') { 188 | return this.props.renderIndicator(props); 189 | } 190 | const { width, height, position, navigationState } = props; 191 | const translateY = Animated.multiply( 192 | Animated.multiply( 193 | position.interpolate({ 194 | inputRange: [0, navigationState.routes.length - 1], 195 | outputRange: [0, navigationState.routes.length - 1], 196 | extrapolate: 'clamp', 197 | }), 198 | height 199 | ), 200 | I18nManager.isRTL ? -1 : 1 201 | ); 202 | return ( 203 | 210 | ); 211 | }; 212 | 213 | _getTabWidth = props => { 214 | const { layout, navigationState, tabStyle } = props; 215 | const flattened = StyleSheet.flatten(tabStyle); 216 | 217 | if (flattened) { 218 | switch (typeof flattened.width) { 219 | case 'number': 220 | return flattened.width; 221 | case 'string': 222 | if (flattened.width.endsWith('%')) { 223 | const width = parseFloat(flattened.width); 224 | if (Number.isFinite(width)) { 225 | return layout.width * (width / 100); 226 | } 227 | } 228 | } 229 | } 230 | 231 | if (props.scrollEnabled) { 232 | return (layout.width / 5) * 2; 233 | } 234 | 235 | return layout.width / navigationState.routes.length; 236 | }; 237 | 238 | _getTabHeight = props => { 239 | const { layout, navigationState, tabStyle } = props; 240 | const flattened = StyleSheet.flatten(tabStyle); 241 | 242 | if (flattened) { 243 | switch (typeof flattened.height) { 244 | case 'number': 245 | return flattened.height; 246 | case 'string': 247 | if (flattened.height.endsWith('%')) { 248 | const height = parseFloat(flattened.height); 249 | if (Number.isFinite(height)) { 250 | return layout.height * (height / 100); 251 | } 252 | } 253 | } 254 | } 255 | 256 | if (props.scrollEnabled) { 257 | return (layout.height / 5) * 2; 258 | } 259 | return layout.height / navigationState.routes.length; 260 | }; 261 | 262 | _handleTabPress = ({ route }: Scene<*>) => { 263 | this._pendingIndex = this.props.navigationState.routes.indexOf(route); 264 | 265 | if (this.props.onTabPress) { 266 | this.props.onTabPress({ route }); 267 | } 268 | 269 | this.props.jumpTo(route.key); 270 | }; 271 | 272 | _normalizeScrollValue = (props, value) => { 273 | const { layout, navigationState } = props; 274 | const tabHeight = this._getTabHeight(props); 275 | const tabBarHeight = Math.max( 276 | tabHeight * navigationState.routes.length, 277 | layout.height 278 | ); 279 | const maxDistance = tabBarHeight - layout.height; 280 | 281 | return Math.max(Math.min(value, maxDistance), 0); 282 | }; 283 | 284 | _getScrollAmount = (props, i) => { 285 | const { layout } = props; 286 | const tabHeight = this._getTabHeight(props); 287 | const centerDistance = tabHeight * (i + 1 / 2); 288 | const scrollAmount = centerDistance - layout.height / 2; 289 | 290 | return this._normalizeScrollValue(props, scrollAmount); 291 | }; 292 | 293 | _adjustScroll = (value: number) => { 294 | if (this.props.scrollEnabled) { 295 | global.cancelAnimationFrame(this._scrollResetCallback); 296 | this._scrollView && 297 | this._scrollView.scrollTo({ 298 | y: this._normalizeScrollValue( 299 | this.props, 300 | this._getScrollAmount(this.props, value) 301 | ), 302 | animated: !this._isIntial, // Disable animation for the initial render 303 | }); 304 | 305 | this._isIntial = false; 306 | } 307 | }; 308 | 309 | _resetScroll = (value: number, animated = true) => { 310 | if (this.props.scrollEnabled) { 311 | global.cancelAnimationFrame(this._scrollResetCallback); 312 | this._scrollResetCallback = global.requestAnimationFrame(() => { 313 | this._scrollView && 314 | this._scrollView.scrollTo({ 315 | y: this._getScrollAmount(this.props, value), 316 | animated, 317 | }); 318 | }); 319 | } 320 | }; 321 | 322 | _handleBeginDrag = () => { 323 | // onScrollBeginDrag fires when user touches the ScrollView 324 | this._isManualScroll = true; 325 | this._isMomentumScroll = false; 326 | }; 327 | 328 | _handleEndDrag = () => { 329 | // onScrollEndDrag fires when user lifts his finger 330 | // onMomentumScrollBegin fires after touch end 331 | // run the logic in next frame so we get onMomentumScrollBegin first 332 | global.requestAnimationFrame(() => { 333 | if (this._isMomentumScroll) { 334 | return; 335 | } 336 | this._isManualScroll = false; 337 | }); 338 | }; 339 | 340 | _handleMomentumScrollBegin = () => { 341 | // onMomentumScrollBegin fires on flick, as well as programmatic scroll 342 | this._isMomentumScroll = true; 343 | }; 344 | 345 | _handleMomentumScrollEnd = () => { 346 | // onMomentumScrollEnd fires when the scroll finishes 347 | this._isMomentumScroll = false; 348 | this._isManualScroll = false; 349 | }; 350 | 351 | render() { 352 | const { position, navigationState, scrollEnabled, bounces } = this.props; 353 | const { routes } = navigationState; 354 | const tabWidth = this._getTabWidth(this.props); 355 | 356 | const tabHeight = this._getTabHeight(this.props); 357 | 358 | // Prepend '-1', so there are always at least 2 items in inputRange 359 | const inputRange = [-1, ...routes.map((x, i) => i)]; 360 | const translateY = Animated.multiply(this.state.scrollAmount, -1); 361 | 362 | return ( 363 | 364 | 373 | {this._renderIndicator({ 374 | ...this.props, 375 | width: tabWidth, 376 | height: tabHeight, 377 | })} 378 | 379 | 380 | (this._scrollView = el && el._component)} 411 | > 412 | {routes.map((route, i) => { 413 | const outputRange = inputRange.map( 414 | inputIndex => (inputIndex === i ? 1 : 0.7) 415 | ); 416 | const opacity = Animated.multiply( 417 | this.state.visibility, 418 | position.interpolate({ 419 | inputRange, 420 | outputRange, 421 | }) 422 | ); 423 | const label = this._renderLabel({ route }); 424 | const icon = this.props.renderIcon 425 | ? this.props.renderIcon({ route }) 426 | : null; 427 | const badge = this.props.renderBadge 428 | ? this.props.renderBadge({ route }) 429 | : null; 430 | 431 | const tabStyle = {}; 432 | 433 | tabStyle.opacity = opacity; 434 | 435 | if (icon) { 436 | if (label) { 437 | tabStyle.paddingTop = 8; 438 | } else { 439 | tabStyle.padding = 12; 440 | } 441 | } 442 | 443 | const passedTabStyle = StyleSheet.flatten(this.props.tabStyle); 444 | 445 | const isHeightSet = 446 | (passedTabStyle && 447 | typeof passedTabStyle.height !== 'undefined') || 448 | scrollEnabled === true; 449 | 450 | const tabContainerStyle = {}; 451 | 452 | if (isHeightSet) { 453 | tabStyle.height = tabHeight; 454 | tabContainerStyle.height = tabHeight; 455 | } 456 | 457 | if (passedTabStyle && typeof passedTabStyle.flex === 'number') { 458 | tabContainerStyle.flex = passedTabStyle.flex; 459 | } else if (!isHeightSet) { 460 | tabContainerStyle.flex = 1; 461 | } 462 | 463 | let accessibilityLabel = this.props.getAccessibilityLabel({ 464 | route, 465 | }); 466 | 467 | accessibilityLabel = 468 | typeof accessibilityLabel !== 'undefined' 469 | ? accessibilityLabel 470 | : this.props.getLabelText({ route }); 471 | 472 | const isFocused = i === navigationState.index; 473 | 474 | return ( 475 | this._handleTabPress({ route })} 489 | style={tabContainerStyle} 490 | > 491 | 492 | 500 | {icon} 501 | {label} 502 | 503 | {badge ? ( 504 | 510 | {badge} 511 | 512 | ) : null} 513 | 514 | 515 | ); 516 | })} 517 | 518 | 519 | 520 | ); 521 | } 522 | } 523 | 524 | const styles = StyleSheet.create({ 525 | container: { 526 | flex: 1, 527 | }, 528 | scroll: { 529 | overflow: Platform.OS === 'web' ? ('auto': any) : 'scroll', 530 | }, 531 | tabBar: { 532 | backgroundColor: '#2196f3', 533 | elevation: 4, 534 | flexDirection: 'row', 535 | shadowColor: 'black', 536 | shadowOpacity: 0.1, 537 | shadowRadius: StyleSheet.hairlineWidth, 538 | shadowOffset: { 539 | height: StyleSheet.hairlineWidth, 540 | }, 541 | // We don't need zIndex on Android, disable it since it's buggy 542 | zIndex: Platform.OS === 'android' ? 0 : 1, 543 | }, 544 | tabContent: { 545 | flexDirection: 'column', 546 | flexWrap: 'nowrap', 547 | }, 548 | tabLabel: { 549 | backgroundColor: 'transparent', 550 | color: 'white', 551 | margin: 8, 552 | }, 553 | tabItem: { 554 | flex: 1, 555 | padding: 8, 556 | alignItems: 'center', 557 | justifyContent: 'center', 558 | }, 559 | badge: { 560 | position: 'absolute', 561 | top: 0, 562 | right: 0, 563 | }, 564 | indicatorContainer: { 565 | position: 'absolute', 566 | top: 0, 567 | left: 0, 568 | right: 0, 569 | bottom: 0, 570 | }, 571 | indicator: { 572 | backgroundColor: '#ffeb3b', 573 | position: 'absolute', 574 | left: 0, 575 | bottom: 0, 576 | right: 0, 577 | height: 2, 578 | }, 579 | }); 580 | -------------------------------------------------------------------------------- /src/TabViewVertical.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { Animated, View, StyleSheet } from 'react-native'; 5 | import TabBarVertical from './TabBarVertical'; 6 | import { PagerDefault } from 'react-native-tab-view'; 7 | 8 | import type { 9 | Scene, 10 | SceneRendererProps, 11 | NavigationState, 12 | Layout, 13 | PagerCommonProps, 14 | PagerExtraProps, 15 | } from 'react-native-tab-view/src/TypeDefinitions'; 16 | import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet'; 17 | 18 | type Props = PagerCommonProps & 19 | PagerExtraProps & { 20 | navigationState: NavigationState, 21 | onIndexChange: (index: number) => mixed, 22 | initialLayout?: Layout, 23 | renderPager: (props: *) => React.Node, 24 | renderScene: (props: SceneRendererProps & Scene) => React.Node, 25 | renderTabBar: (props: SceneRendererProps) => React.Node, 26 | tabBarPosition: 'top' | 'bottom', 27 | useNativeDriver?: boolean, 28 | style?: ViewStyleProp, 29 | }; 30 | 31 | type State = {| 32 | sceneLayout: Layout & { measured: boolean }, 33 | windowLayout: Layout & { measured: boolean }, 34 | layoutXY: Animated.ValueXY, 35 | panX: Animated.Value, 36 | offsetX: Animated.Value, 37 | position: any, 38 | |}; 39 | 40 | export default class TabViewVertical extends React.Component< 41 | Props, 42 | State 43 | > { 44 | static defaultProps = { 45 | canJumpToTab: () => true, 46 | tabBarPosition: 'top', 47 | renderTabBar: (props: *) => , 48 | renderPager: (props: *) => , 49 | getTestID: ({ route }: Scene<*>) => 50 | typeof route.testID === 'string' ? route.testID : undefined, 51 | initialLayout: { 52 | height: 0, 53 | width: 0, 54 | }, 55 | useNativeDriver: false, 56 | }; 57 | 58 | constructor(props: Props) { 59 | super(props); 60 | 61 | const { navigationState } = this.props; 62 | const layout = { 63 | ...this.props.initialLayout, 64 | measured: false, 65 | }; 66 | const panX = new Animated.Value(0); 67 | const offsetX = new Animated.Value(-navigationState.index * layout.width); 68 | const layoutXY = new Animated.ValueXY({ 69 | // This is hacky, but we need to make sure that the value is never 0 70 | x: layout.width || 0.001, 71 | y: layout.height || 0.001, 72 | }); 73 | const position = Animated.multiply( 74 | Animated.divide(Animated.add(panX, offsetX), layoutXY.x), 75 | -1 76 | ); 77 | 78 | this.state = { 79 | sceneLayout: layout, 80 | windowLayout: layout, 81 | layoutXY, 82 | panX, 83 | offsetX, 84 | position, 85 | }; 86 | } 87 | 88 | componentDidMount() { 89 | this._mounted = true; 90 | } 91 | 92 | componentWillUnmount() { 93 | this._mounted = false; 94 | } 95 | 96 | _mounted: boolean = false; 97 | _nextIndex: ?number; 98 | 99 | _renderScene = (props: SceneRendererProps & Scene) => { 100 | return this.props.renderScene(props); 101 | }; 102 | 103 | _handleLayoutScene = (e: any) => { 104 | const { height, width } = e.nativeEvent.layout; 105 | 106 | if ( 107 | this.state.sceneLayout.width === width && 108 | this.state.sceneLayout.height === height 109 | ) { 110 | return; 111 | } 112 | 113 | this.state.offsetX.setValue(-this.props.navigationState.index * width); 114 | this.state.layoutXY.setValue({ 115 | // This is hacky, but we need to make sure that the value is never 0 116 | x: width || 0.001, 117 | y: height || 0.001, 118 | }); 119 | 120 | this.setState({ 121 | sceneLayout: { 122 | measured: true, 123 | height, 124 | width, 125 | }, 126 | }); 127 | }; 128 | 129 | _handleLayoutWindow = (e: any) => { 130 | const { height, width } = e.nativeEvent.layout; 131 | 132 | if ( 133 | this.state.windowLayout.width === width && 134 | this.state.windowLayout.height === height 135 | ) { 136 | return; 137 | } 138 | 139 | this.setState({ 140 | windowLayout: { 141 | measured: true, 142 | height, 143 | width, 144 | }, 145 | }); 146 | }; 147 | 148 | _buildSceneRendererProps = (): SceneRendererProps<*> => ({ 149 | panX: this.state.panX, 150 | offsetX: this.state.offsetX, 151 | position: this.state.position, 152 | layout: this.state.sceneLayout, 153 | navigationState: this.props.navigationState, 154 | jumpTo: this._jumpTo, 155 | useNativeDriver: this.props.useNativeDriver === true, 156 | }); 157 | 158 | _jumpTo = (key: string) => { 159 | if (!this._mounted) { 160 | // We are no longer mounted, this is a no-op 161 | return; 162 | } 163 | 164 | const { canJumpToTab, navigationState } = this.props; 165 | const index = navigationState.routes.findIndex(route => route.key === key); 166 | 167 | if (!canJumpToTab(navigationState.routes[index])) { 168 | return; 169 | } 170 | 171 | if (index !== navigationState.index) { 172 | this.props.onIndexChange(index); 173 | } 174 | }; 175 | 176 | render() { 177 | const { 178 | /* eslint-disable no-unused-vars */ 179 | navigationState, 180 | onIndexChange, 181 | initialLayout, 182 | renderScene, 183 | /* eslint-enable no-unused-vars */ 184 | renderPager, 185 | renderTabBar, 186 | tabBarPosition, 187 | ...rest 188 | } = this.props; 189 | 190 | const props = this._buildSceneRendererProps(); 191 | const tabBarLayout = { 192 | ...this.state.windowLayout, 193 | width: this.state.windowLayout.width - this.state.sceneLayout.width, 194 | }; 195 | 196 | return ( 197 | 202 | {(tabBarPosition === 'top' || tabBarPosition === 'left') && 203 | renderTabBar({ ...props, layout: tabBarLayout })} 204 | 205 | {renderPager({ 206 | ...props, 207 | ...rest, 208 | panX: this.state.panX, 209 | offsetX: this.state.offsetX, 210 | children: navigationState.routes.map(route => { 211 | const scene = this._renderScene({ 212 | ...props, 213 | route, 214 | }); 215 | 216 | if (React.isValidElement(scene)) { 217 | /* $FlowFixMe: https://github.com/facebook/flow/issues/4775 */ 218 | return React.cloneElement(scene, { key: route.key }); 219 | } 220 | 221 | return scene; 222 | }), 223 | })} 224 | 225 | {(tabBarPosition === 'bottom' || tabBarPosition === 'right') && 226 | renderTabBar({ ...props, layout: tabBarLayout })} 227 | 228 | ); 229 | } 230 | } 231 | 232 | const styles = StyleSheet.create({ 233 | container: { 234 | flex: 1, 235 | overflow: 'hidden', 236 | flexDirection: 'row', 237 | }, 238 | pager: { 239 | flex: 1, 240 | }, 241 | }); 242 | -------------------------------------------------------------------------------- /src/TouchableItem.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import * as React from 'react'; 4 | import { 5 | TouchableNativeFeedback, 6 | TouchableOpacity, 7 | Platform, 8 | View, 9 | } from 'react-native'; 10 | import type { ViewStyleProp } from 'react-native/Libraries/StyleSheet/StyleSheet'; 11 | 12 | const LOLLIPOP = 21; 13 | 14 | type Props = { 15 | onPress: () => mixed, 16 | delayPressIn?: number, 17 | borderless?: boolean, 18 | pressColor?: string, 19 | pressOpacity?: number, 20 | children?: React.Node, 21 | style?: ViewStyleProp, 22 | }; 23 | 24 | export default class TouchableItem extends React.Component { 25 | static defaultProps = { 26 | pressColor: 'rgba(255, 255, 255, .4)', 27 | }; 28 | 29 | render() { 30 | const { style, pressOpacity, pressColor, borderless, ...rest } = this.props; 31 | 32 | if (Platform.OS === 'android' && Platform.Version >= LOLLIPOP) { 33 | return ( 34 | 38 | {React.Children.only(this.props.children)} 39 | 40 | ); 41 | } else { 42 | return ( 43 | 44 | {this.props.children} 45 | 46 | ); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { 4 | TabView, 5 | TabBar, 6 | PagerDefault, 7 | PagerScroll, 8 | PagerAndroid, 9 | PagerExperimental, 10 | SceneMap, 11 | } from 'react-native-tab-view'; 12 | 13 | import TabViewVertical from './TabViewVertical'; 14 | import TabBarVertical from './TabBarVertical'; 15 | 16 | export { 17 | TabView, 18 | TabViewVertical, 19 | TabBar, 20 | TabBarVertical, 21 | PagerDefault, 22 | PagerScroll, 23 | PagerAndroid, 24 | PagerExperimental, 25 | SceneMap, 26 | }; 27 | 28 | export type { 29 | Route, 30 | Scene, 31 | NavigationState, 32 | SceneRendererProps, 33 | } from 'react-native-tab-view'; 34 | --------------------------------------------------------------------------------