Compare commits

..

5 Commits

Author SHA1 Message Date
Eric Vicenti
0ea01673e5 transitioner2
Introducing a wild and hacky prototype of Transitioner 2!

- "transitionProps" concept is now identical to transitioner.state
- Far simpler state management than before
- Descriptors only, no "scenes"
- No more "position"

Also, StackView is seeing improvements:
- Easier to understand when "transition props" are just this.props

- StackView only renders the current sceen and the one before it

Currently broken:
- Interpolation configuration, beyond the first push
- Attempting to move away from getSceneIndicesForInterpolationInputRange because it works with position and it is confusing as hell, but haven't worked around it yet
- Gestures, although some "backProgress" code is in the works
- Interpolation is super buggy
- onTransitionStart/End
- Header, although a lot of code is moved over
2018-03-11 00:11:58 -08:00
Brent Vatne
71adb7cc4f Update DrawerView.js 2018-03-06 14:22:31 -08:00
Eric Vicenti
77343cb096 Merge branch 'master' into @ericvicenti/drawer-fix 2018-03-06 11:43:25 -08:00
Brent Vatne
accee76951 Merge branch 'master' into @ericvicenti/drawer-fix 2018-03-05 16:23:10 -08:00
Eric Vicenti
bbb8c4d8d3 Fix issue in drawer actions 2018-03-05 11:38:13 -08:00
182 changed files with 31979 additions and 12891 deletions

View File

@@ -7,18 +7,18 @@
"prettier/react"
],
"parser": "babel-eslint",
"plugins": ["react", "prettier"],
"plugins": [
"react",
"prettier"
],
"env": {
"jasmine": true
},
"rules": {
"prettier/prettier": [
"error",
{
"trailingComma": "es5",
"singleQuote": true
}
],
"prettier/prettier": ["error", {
"trailingComma": "es5",
"singleQuote": true
}],
"no-underscore-dangle": "off",
"no-use-before-define": "off",
@@ -27,20 +27,24 @@
"no-plusplus": "off",
"no-class-assign": "off",
"no-duplicate-imports": "off",
"import/extensions": "off",
"import/no-extraneous-dependencies": "off",
"import/no-unresolved": "off",
"react/jsx-filename-extension": ["off", { "extensions": [".js", ".jsx"] }],
"react/jsx-filename-extension": [
"off", { "extensions": [".js", ".jsx"] }
],
"react/sort-comp": "off",
"react/prefer-stateless-function": "off",
"react/no-deprecated": "off",
"react/forbid-prop-types": "warn",
"react/prop-types": "off",
"react/require-default-props": "off",
"react/no-unused-prop-types": "off"
"react/no-unused-prop-types": "off",
},
"settings": {
},
"parserOptions": {
"ecmaVersion": 6,

View File

@@ -25,7 +25,7 @@ Bugs with react-navigation must be reproducible *without any external libraries
### How to reproduce
- You must provide a way to reproduce the problem. If you are having an issue with your machine or build tools, the issue belongs on another repository as that is outside of the scope of React Navigation.
- You must provide a way to reproduce the problem. If you are having an issue with your machine or build tools, the issue belongs on another repoistory as that is outside of the scope of Rect Navigation.
- Either re-create the bug on [Snack](https://snack.expo.io) or link to a GitHub repository with code that reproduces the bug.
- Explain how to run the example app and any steps that we need to take to reproduce the issue from the example app.

View File

@@ -1,21 +1,17 @@
Please provide enough information so that others can review your pull request:
## Motivation
Explain the **motivation** for making this change. What existing problem does the pull request solve?
## Test plan
Prefer **small pull requests**. These are much easier to review and more likely to get merged. Make sure the PR does only one thing, otherwise split it.
Demonstrate the code is solid. Example: the exact commands you ran and their output, screenshots / videos if the pull request changes UI.
**Test plan (required)**
Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI.
Make sure you test on both platforms if your change affects both platforms.
The code must pass tests.
## Code formatting
**Code formatting**
Look around. Match the style of the rest of the codebase. Run `yarn format` before committing.
## Changelog
Add an entry under the "Unreleased" heading in [CHANGELOG.md](https://github.com/react-navigation/react-navigation/blob/master/CHANGELOG.md#unreleased) which explains your change.
Look around. Match the style of the rest of the codebase.

View File

@@ -1,15 +0,0 @@
{
"increment": "conventional:angular",
"changelogCommand": "conventional-changelog -p angular | tail -n +3",
"safeBump": false,
"src": {
"commitMessage": "chore: release %s",
"tagName": "v%s"
},
"npm": {
"publish": true
},
"github": {
"release": true
}
}

View File

@@ -1,132 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [3.1.5] - [2019-02-06](https://github.com/react-navigation/react-navigation/releases/tag/3.1.5)
## Fixed
- Revert "Transparent header measurement fix (https://github.com/react-navigation/react-navigation-stack/pull/71)"
## [3.1.4] - [2019-02-05](https://github.com/react-navigation/react-navigation/releases/tag/3.1.4)
## Fixed
- Fix references to onGestureFinish in StackViewLayout, should be onGestureEnd
## [3.1.3] - [2019-02-04](https://github.com/react-navigation/react-navigation/releases/tag/3.1.3)
## Fixed
- Stack navigator properly dismisses and restores keyboard when gesture starts and is cancelled
- Transparent header measurement fix (https://github.com/react-navigation/react-navigation-stack/pull/71)
## [3.1.2] - [2019-02-01](https://github.com/react-navigation/react-navigation/releases/tag/3.1.2)
## Fixed
- Update flow definition for `withNavigation` and `withNavigationFocus` to support `defaultProps`
- Prevent onRef callback be called twice on withNavigationFocus components (https://github.com/react-navigation/react-navigation-core/pull/30)
- Bump react-navigation-drawer version to improve performance - if you use Expo, you will need expo@^32.0.3 to update!
## [3.0.9] - [2018-12-19](https://github.com/react-navigation/react-navigation/releases/tag/3.0.9)
## Fixed
- Intermittent flicker when changing tabs while using react-native-screens fixed by not changing opacity (https://github.com/react-navigation/react-navigation-tabs/pull/80)
- Prevent fading the previous screen on push/pop on Android (https://github.com/react-navigation/react-navigation-stack/pull/73)
## [3.0.8] - [2018-12-08](https://github.com/react-navigation/react-navigation/releases/tag/3.0.8)
## Changed
- Lock create-react-context to 0.2.2
## [3.0.7] - [2018-12-08](https://github.com/react-navigation/react-navigation/releases/tag/3.0.7)
## Changed
- Optimize stack gesture to avoid a setState and reduce unnecessary Animated node creation (https://github.com/react-navigation/react-navigation-stack/pull/70)
## [3.0.6] - [2018-12-06](https://github.com/react-navigation/react-navigation/releases/tag/3.0.6)
## Fixes
- Fix drawer accessibility label when drawer label is not a string
## [3.0.5] - [2018-12-03](https://github.com/react-navigation/react-navigation/releases/tag/3.0.5)
## Fixes
- Fix crash in rare case where onNavigationStateChange on container leads to setState and container has screenProps (https://github.com/react-navigation/react-navigation/issues/5301)
- Expose underlaying ScrollView methods to NavigationAwareScrollable (https://github.com/react-navigation/react-navigation-native/pull/8)
## [3.0.4] - [2018-11-30](https://github.com/react-navigation/react-navigation/releases/tag/3.0.4)
## Changed
- Lock dependencies to exact versions
## Fixes
- Fix crash when screenInterpolator is null (https://github.com/react-navigation/react-navigation-stack/issues/64)
- Fix renderPager override (https://github.com/react-navigation/react-navigation-tabs/pull/70)
## Added
- Accessibility labels on drawer items (https://github.com/react-navigation/react-navigation-drawer/pull/30)
## [3.0.3] - [2018-11-30](https://github.com/react-navigation/react-navigation/releases/tag/3.0.3)
## Fixes
- Fix bug where if you navigate immediately when the navigator is first mounted the stack could get in an invalid state.
- Transparent stack card factors in header height now, even though you probably won't want to use this.
- Fix bug where shadow was still rendered on transparent stack
- Fix gestureResponseDistance custom values being ignored for modal stack
## [3.0.2] - [2018-11-27](https://github.com/react-navigation/react-navigation/releases/tag/3.0.2)
## Fixes
- Fix `drawerLockMode` on drawer navigator
- Fix RTL support in drawer navigator
## [3.0.1] - [2018-11-26](https://github.com/react-navigation/react-navigation/releases/tag/3.0.1)
## Fixes
- fix NavigationTestUtils.js deprecated file import.
- Update `getParam` flow typings to check `key` and `fallback` arguments, as well as return the correct type automatically.
- Fix regression in backgroundColor on cardStyle for stack navigator.
## [3.0.0] - [2018-11-17](https://github.com/react-navigation/react-navigation/releases/tag/3.0.0)
- Changes between the latest 2.x release and 3.0.0 are listed on the blog at https://reactnavigation.org/blog/2018/11/17/react-navigation-3.0.html
# [Previous major versions]
- [2.x](https://github.com/react-navigation/react-navigation/blob/2.x/CHANGELOG.md)
[Unreleased]: https://github.com/react-navigation/react-navigation/compare/3.1.5...HEAD
[3.1.5]: https://github.com/react-navigation/react-navigation/compare/3.1.4...3.1.5
[3.1.4]: https://github.com/react-navigation/react-navigation/compare/3.1.3...3.1.4
[3.1.3]: https://github.com/react-navigation/react-navigation/compare/3.1.2...3.1.3
[3.1.2]: https://github.com/react-navigation/react-navigation/compare/3.0.9...3.1.2
[3.0.9]: https://github.com/react-navigation/react-navigation/compare/3.0.8...3.0.9
[3.0.8]: https://github.com/react-navigation/react-navigation/compare/3.0.7...3.0.8
[3.0.7]: https://github.com/react-navigation/react-navigation/compare/3.0.6...3.0.7
[3.0.6]: https://github.com/react-navigation/react-navigation/compare/3.0.5...3.0.6
[3.0.5]: https://github.com/react-navigation/react-navigation/compare/3.0.4...3.0.5
[3.0.4]: https://github.com/react-navigation/react-navigation/compare/3.0.3...3.0.4
[3.0.3]: https://github.com/react-navigation/react-navigation/compare/3.0.2...3.0.3
[3.0.2]: https://github.com/react-navigation/react-navigation/compare/3.0.1...3.0.2
[3.0.1]: https://github.com/react-navigation/react-navigation/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/react-navigation/react-navigation/compare/2.x...3.0.0

View File

@@ -1,7 +0,0 @@
import { _TESTING_ONLY_reset_container_count } from '@react-navigation/native/src/createAppContainer';
export default {
resetInternalState: () => {
_TESTING_ONLY_reset_container_count();
},
};

View File

@@ -1,12 +1,22 @@
# React Navigation
[![npm version](https://badge.fury.io/js/react-navigation.svg)](https://badge.fury.io/js/react-navigation) [![CircleCI badge](https://circleci.com/gh/react-navigation/react-navigation/tree/master.svg?style=shield)](https://circleci.com/gh/react-navigation/react-navigation/tree/master) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://reactnavigation.org/docs/contributing.html)
[![npm version](https://badge.fury.io/js/react-navigation.svg)](https://badge.fury.io/js/react-navigation) [![codecov](https://codecov.io/gh/react-community/react-navigation/branch/master/graph/badge.svg)](https://codecov.io/gh/react-community/react-navigation) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://reactnavigation.org/docs/guides/contributors)
React Navigation is born from the React Native community's need for an extensible yet easy-to-use navigation solution based on Javascript.
## Installation
See: https://reactnavigation.org/docs/en/getting-started.html
Since the library is a JS-based solution, to install the latest version of react-navigation you only need to run:
```bash
yarn add react-navigation
```
or
```bash
npm install --save react-navigation
```
## Documentation
@@ -45,4 +55,4 @@ This library has adopted a Code of Conduct that we expect project participants t
## License
React Navigation is licensed under the [BSD 2-clause "Simplified" License](https://github.com/react-community/react-navigation/blob/master/LICENSE).
React-navigation is licensed under the [BSD 2-clause "Simplified" License](https://github.com/react-community/react-navigation/blob/master/LICENSE).

View File

@@ -1,12 +0,0 @@
/**
* This file is needed to hijack asset imports so that test files don't attempt
* to import them as JavaScript modules.
* See https://github.com/facebook/jest/issues/2663#issuecomment-317109798
*/
const path = require('path');
module.exports = {
process(src, filename, config, options) {
return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
},
};

View File

@@ -1,5 +1,17 @@
{
"presets": [
"expo"
]
"babel-preset-expo"
],
"env": {
"development": {
"plugins": [
"transform-react-jsx-source"
],
},
"production": {
"plugins": [
"transform-remove-console"
]
}
}
}

View File

@@ -55,6 +55,8 @@ module.system=haste
emoji=true
experimental.strict_type_args=true
munge_underscores=true
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'
@@ -75,5 +77,7 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(<VERSION>\\)? *\\(site=[a-z,_]*
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
unsafe.enable_getters_and_setters=true
[version]
^0.67.0
^0.61.0

View File

@@ -1,9 +1,2 @@
import { Platform } from 'react-native';
import { useScreens } from 'react-native-screens';
if (Platform.OS === 'android') {
// useScreens();
}
import App from './js/App';
export default App;

View File

@@ -5,6 +5,5 @@ import renderer from 'react-test-renderer';
it('renders without crashing', () => {
const rendered = renderer.create(<App />).toJSON();
// Will be null because the playground uses state persistence which happens asyncronously
expect(rendered).toEqual(null);
expect(rendered).toBeTruthy();
});

View File

@@ -1,14 +1,9 @@
# Navigation Playground Example
The NavigationPlayground example app includes a variety of patterns and is used as a simple way for contributors to manually integration test changes.
A playground for experimenting with react-navigation in a pure-JS React Native app.
## Usage
```bash
yarn # in the react-navigation root directory
cd examples/NavigationPlayground
yarn
yarn start
```
Please see the [Contributors Guide](https://reactnavigation.org/docs/guides/contributors#Run-the-Example-App) for instructions on running these example apps.
You can view this example application directly on Android phones by visiting scanning the QR code on [this site](https://exp.host/@react-navigation/NavigationPlayground) with the [Expo app](https://play.google.com/store/apps/details?id=host.exp.exponent&hl=en).
You can view this example application directly on your phone by visiting [our expo demo](https://exp.host/@react-navigation/NavigationPlayground).

View File

@@ -11,16 +11,16 @@
"splash": {
"image": "./assets/icons/splash.png"
},
"sdkVersion": "32.0.0",
"assetBundlePatterns": [
"**/*"
],
"ios": {
"bundleIdentifier": "com.reactnavigation.example",
"supportsTablet": true
"sdkVersion": "25.0.0",
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"packagerOpts": {
"assetExts": [
"ttf",
"mp4"
]
},
"android": {
"package": "com.reactnavigation.example"
"ios": {
"supportsTablet": true
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -11,20 +11,12 @@ import {
Platform,
ScrollView,
StyleSheet,
TouchableOpacity,
Text,
StatusBar,
View,
} from 'react-native';
import {
RectButton,
NativeViewGestureHandler,
} from 'react-native-gesture-handler';
import {
createAppContainer,
SafeAreaView,
createStackNavigator,
} from 'react-navigation';
import { Assets as StackAssets } from 'react-navigation-stack';
import { SafeAreaView, StackNavigator } from 'react-navigation';
import CustomTabs from './CustomTabs';
import CustomTransitioner from './CustomTransitioner';
@@ -34,40 +26,19 @@ import TabsInDrawer from './TabsInDrawer';
import ModalStack from './ModalStack';
import StacksInTabs from './StacksInTabs';
import StacksOverTabs from './StacksOverTabs';
import StacksOverTopTabs from './StacksOverTopTabs';
import StacksWithKeys from './StacksWithKeys';
import InactiveStack from './InactiveStack';
import StackWithCustomHeaderBackImage from './StackWithCustomHeaderBackImage';
import SimpleStack from './SimpleStack';
import StackWithHeaderPreset from './StackWithHeaderPreset';
import StackWithTranslucentHeader from './StackWithTranslucentHeader';
import SimpleTabs from './SimpleTabs';
import CustomTabUI from './CustomTabUI';
import SwitchWithStacks from './SwitchWithStacks';
import TabAnimations from './TabAnimations';
import TabsWithNavigationFocus from './TabsWithNavigationFocus';
import TabsWithNavigationEvents from './TabsWithNavigationEvents';
import KeyboardHandlingExample from './KeyboardHandlingExample';
process.env.REACT_NAV_LOGGING = true;
const ExampleInfo = {
SimpleStack: {
name: 'Stack Example',
description: 'A card stack',
},
SwitchWithStacks: {
name: 'Switch between routes',
description: 'Jump between routes',
},
InactiveStack: {
name: 'Navigate idempotently to stacks in inactive routes',
description:
'An inactive route in a stack should be given the opportunity to handle actions',
},
StackWithCustomHeaderBackImage: {
name: 'Custom header back image',
description: 'Stack with custom header back image',
},
SimpleTabs: {
name: 'Tabs Example',
description: 'Tabs following platform conventions',
@@ -80,6 +51,10 @@ const ExampleInfo = {
name: 'UIKit-style Header Transitions',
description: 'Masked back button and sliding header items. iOS only.',
},
StackWithHeaderPreset: {
name: 'UIKit-style Header Transitions',
description: 'Masked back button and sliding header items. iOS only.',
},
StackWithTranslucentHeader: {
name: 'Translucent Header',
description: 'Render arbitrary translucent content in header background.',
@@ -118,10 +93,6 @@ const ExampleInfo = {
name: 'Stacks over Tabs',
description: 'Nested stack navigation that pushes on top of tabs',
},
StacksOverTopTabs: {
name: 'Stacks with non-standard header height',
description: 'Tab navigator in stack with custom header heights',
},
StacksWithKeys: {
name: 'Link in Stack with keys',
description: 'Use keys to link between screens',
@@ -134,41 +105,24 @@ const ExampleInfo = {
name: 'Link to Settings Tab',
description: 'Deep linking into a route in tab',
},
TabAnimations: {
name: 'Animated Tabs Example',
description: 'Tab transitions have custom animations',
},
TabsWithNavigationFocus: {
name: 'withNavigationFocus',
description: 'Receive the focus prop to know when a screen is focused',
},
TabsWithNavigationEvents: {
name: 'NavigationEvents',
description:
'Declarative NavigationEvents component to subscribe to navigation events',
},
KeyboardHandlingExample: {
name: 'Keyboard Handling Example',
description:
'Demo automatic handling of keyboard showing/hiding inside StackNavigator',
},
CustomTabUI: {
name: 'Custom Tabs UI',
description: 'Render additional views around a Tab navigator',
},
};
const ExampleRoutes = {
SimpleStack,
SwitchWithStacks,
SimpleStack: SimpleStack,
SimpleTabs: SimpleTabs,
Drawer: Drawer,
// MultipleDrawer: {
// screen: MultipleDrawer,
// },
StackWithCustomHeaderBackImage: StackWithCustomHeaderBackImage,
...Platform.select({
ios: {
StackWithHeaderPreset: StackWithHeaderPreset,
},
android: {},
}),
StackWithHeaderPreset: StackWithHeaderPreset,
StackWithTranslucentHeader: StackWithTranslucentHeader,
TabsInDrawer: TabsInDrawer,
CustomTabs: CustomTabs,
@@ -176,9 +130,7 @@ const ExampleRoutes = {
ModalStack: ModalStack,
StacksWithKeys: StacksWithKeys,
StacksInTabs: StacksInTabs,
CustomTabUI: CustomTabUI,
StacksOverTabs: StacksOverTabs,
StacksOverTopTabs: StacksOverTopTabs,
LinkStack: {
screen: SimpleStack,
path: 'people/Jordan',
@@ -187,11 +139,8 @@ const ExampleRoutes = {
screen: SimpleTabs,
path: 'settings',
},
TabAnimations,
TabsWithNavigationFocus,
TabsWithNavigationEvents,
KeyboardHandlingExample,
// This is commented out because it's rarely useful
// InactiveStack,
};
type State = {
@@ -202,8 +151,13 @@ class MainScreen extends React.Component<any, State> {
scrollY: new Animated.Value(0),
};
componentDidMount() {
Asset.loadAsync(StackAssets);
componentWillMount() {
Asset.fromModule(
require('react-navigation/src/views/assets/back-icon-mask.png')
).downloadAsync();
Asset.fromModule(
require('react-navigation/src/views/assets/back-icon.png')
).downloadAsync();
}
render() {
@@ -245,72 +199,69 @@ class MainScreen extends React.Component<any, State> {
return (
<View style={{ flex: 1 }}>
<NativeViewGestureHandler>
<Animated.ScrollView
style={{ flex: 1, backgroundColor: '#eee' }}
scrollEventThrottle={1}
onScroll={Animated.event(
[
{
nativeEvent: { contentOffset: { y: this.state.scrollY } },
},
],
{ useNativeDriver: true }
)}
<Animated.ScrollView
style={{ flex: 1 }}
scrollEventThrottle={1}
onScroll={Animated.event(
[
{
nativeEvent: { contentOffset: { y: this.state.scrollY } },
},
],
{ useNativeDriver: true }
)}
>
<Animated.View
style={[
styles.backgroundUnderlay,
{
transform: [
{ scale: backgroundScale },
{ translateY: backgroundTranslateY },
],
},
]}
/>
<Animated.View
style={{ opacity, transform: [{ scale }, { translateY }] }}
>
<Animated.View
style={[
styles.backgroundUnderlay,
{
transform: [
{ scale: backgroundScale },
{ translateY: backgroundTranslateY },
],
},
]}
/>
<Animated.View
style={{ opacity, transform: [{ scale }, { translateY }] }}
>
<SafeAreaView
style={styles.bannerContainer}
forceInset={{ top: 'always', bottom: 'never' }}
>
<View style={styles.banner}>
<Image
source={require('./assets/NavLogo.png')}
style={styles.bannerImage}
/>
<Text style={styles.bannerTitle}>
React Navigation Examples
</Text>
</View>
</SafeAreaView>
</Animated.View>
<SafeAreaView
forceInset={{ top: 'never', bottom: 'always' }}
style={{ backgroundColor: '#eee' }}
style={styles.bannerContainer}
forceInset={{ top: 'always', bottom: 'never' }}
>
<View style={{ backgroundColor: '#fff' }}>
{Object.keys(ExampleRoutes).map((routeName: string) => (
<RectButton
key={routeName}
underlayColor="#ccc"
activeOpacity={0.3}
onPress={() => {
let route = ExampleRoutes[routeName];
if (route.screen || route.path || route.params) {
const { path, params, screen } = route;
const { router } = screen;
const action =
path &&
router.getActionForPathAndParams(path, params);
navigation.navigate(routeName, {}, action);
} else {
navigation.navigate(routeName);
}
}}
<View style={styles.banner}>
<Image
source={require('./assets/NavLogo.png')}
style={styles.bannerImage}
/>
<Text style={styles.bannerTitle}>
React Navigation Examples
</Text>
</View>
</SafeAreaView>
</Animated.View>
<SafeAreaView forceInset={{ bottom: 'always', horizontal: 'never' }}>
<View style={{ backgroundColor: '#fff' }}>
{Object.keys(ExampleRoutes).map((routeName: string) => (
<TouchableOpacity
key={routeName}
onPress={() => {
let route = ExampleRoutes[routeName];
if (route.screen || route.path || route.params) {
const { path, params, screen } = route;
const { router } = screen;
const action =
path && router.getActionForPathAndParams(path, params);
navigation.navigate(routeName, {}, action);
} else {
navigation.navigate(routeName);
}
}}
>
<SafeAreaView
style={styles.itemContainer}
forceInset={{ veritcal: 'never', bottom: 'never' }}
>
<View style={styles.item}>
<Text style={styles.title}>
@@ -320,12 +271,12 @@ class MainScreen extends React.Component<any, State> {
{ExampleInfo[routeName].description}
</Text>
</View>
</RectButton>
))}
</View>
</SafeAreaView>
</Animated.ScrollView>
</NativeViewGestureHandler>
</SafeAreaView>
</TouchableOpacity>
))}
</View>
</SafeAreaView>
</Animated.ScrollView>
<StatusBar barStyle="light-content" />
<Animated.View
style={[styles.statusBarUnderlay, { opacity: underlayOpacity }]}
@@ -335,37 +286,35 @@ class MainScreen extends React.Component<any, State> {
}
}
const AppNavigator = createAppContainer(
createStackNavigator(
{
...ExampleRoutes,
Index: {
screen: MainScreen,
},
const AppNavigator = StackNavigator(
{
...ExampleRoutes,
Index: {
screen: MainScreen,
},
{
initialRouteName: 'Index',
headerMode: 'none',
},
{
initialRouteName: 'Index',
headerMode: 'none',
/*
* Use modal on iOS because the card mode comes from the right,
* which conflicts with the drawer example gesture
*/
mode: Platform.OS === 'ios' ? 'modal' : 'card',
}
)
/*
* Use modal on iOS because the card mode comes from the right,
* which conflicts with the drawer example gesture
*/
mode: Platform.OS === 'ios' ? 'modal' : 'card',
}
);
export default class App extends React.Component {
render() {
return <AppNavigator /* persistenceKey="if-you-want-it" */ />;
}
}
// export default () => <AppNavigator />;
export default SimpleStack;
const styles = StyleSheet.create({
item: {
paddingHorizontal: 16,
paddingVertical: 12,
},
itemContainer: {
backgroundColor: '#fff',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#ddd',
},

View File

@@ -1,129 +0,0 @@
import React from 'react';
import {
LayoutAnimation,
View,
StyleSheet,
StatusBar,
Text,
} from 'react-native';
import { SafeAreaView, createMaterialTopTabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import { Button } from './commonComponents/ButtonWithMargin';
class MyHomeScreen extends React.Component {
static navigationOptions = {
tabBarLabel: 'Home',
tabBarIcon: ({ tintColor, focused, horizontal }) => (
<Ionicons
name={focused ? 'ios-home' : 'ios-home'}
size={horizontal ? 20 : 26}
style={{ color: tintColor }}
/>
),
};
render() {
const { navigation } = this.props;
return (
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
<Text>Home Screen</Text>
<Button
onPress={() => navigation.navigate('Home')}
title="Go to home tab"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
</SafeAreaView>
);
}
}
class ReccomendedScreen extends React.Component {
static navigationOptions = {
tabBarLabel: 'Reccomended',
tabBarIcon: ({ tintColor, focused, horizontal }) => (
<Ionicons
name={focused ? 'ios-people' : 'ios-people'}
size={horizontal ? 20 : 26}
style={{ color: tintColor }}
/>
),
};
render() {
const { navigation } = this.props;
return (
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
<Text>Reccomended Screen</Text>
<Button
onPress={() => navigation.navigate('Home')}
title="Go to home tab"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
</SafeAreaView>
);
}
}
class FeaturedScreen extends React.Component {
static navigationOptions = ({ navigation }) => ({
tabBarLabel: 'Featured',
tabBarIcon: ({ tintColor, focused, horizontal }) => (
<Ionicons
name={focused ? 'ios-star' : 'ios-star'}
size={horizontal ? 20 : 26}
style={{ color: tintColor }}
/>
),
});
render() {
const { navigation } = this.props;
return (
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
<Text>Featured Screen</Text>
<Button
onPress={() => navigation.navigate('Home')}
title="Go to home tab"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
</SafeAreaView>
);
}
}
const SimpleTabs = createMaterialTopTabNavigator({
Home: MyHomeScreen,
Reccomended: ReccomendedScreen,
Featured: FeaturedScreen,
});
class TabNavigator extends React.Component {
static router = SimpleTabs.router;
componentWillUpdate() {
LayoutAnimation.easeInEaseOut();
}
render() {
const { navigation } = this.props;
const { routes, index } = navigation.state;
const activeRoute = routes[index];
let bottom = null;
if (activeRoute.routeName !== 'Home') {
bottom = (
<View style={{ height: 50, borderTopWidth: StyleSheet.hairlineWidth }}>
<Button title="Check out" onPress={() => {}} />
</View>
);
}
return (
<View style={{ flex: 1 }}>
<StatusBar barStyle="default" />
<SafeAreaView
style={{ flex: 1 }}
forceInset={{ horizontal: 'always', top: 'always' }}
>
<SimpleTabs navigation={navigation} />
</SafeAreaView>
{bottom}
</View>
);
}
}
export default TabNavigator;

View File

@@ -4,18 +4,22 @@
import React from 'react';
import {
Button,
Platform,
ScrollView,
StyleSheet,
StatusBar,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { BorderlessButton } from 'react-native-gesture-handler';
import { createNavigator, SafeAreaView, TabRouter } from 'react-navigation';
import { createAppContainer } from 'react-navigation';
import {
createNavigator,
createNavigationContainer,
SafeAreaView,
TabRouter,
} from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
@@ -49,13 +53,13 @@ const CustomTabBar = ({ navigation }) => {
return (
<SafeAreaView style={styles.tabContainer}>
{routes.map(route => (
<BorderlessButton
<TouchableOpacity
onPress={() => navigation.navigate(route.routeName)}
style={styles.tab}
key={route.routeName}
>
<Text>{route.routeName}</Text>
</BorderlessButton>
</TouchableOpacity>
))}
</SafeAreaView>
);
@@ -94,7 +98,7 @@ const CustomTabRouter = TabRouter(
}
);
const CustomTabs = createAppContainer(
const CustomTabs = createNavigationContainer(
createNavigator(CustomTabView, CustomTabRouter, {})
);

View File

@@ -1,6 +1,7 @@
import React, { Component, PropTypes } from 'react';
import {
Animated,
Button,
Easing,
Image,
Platform,
@@ -9,14 +10,13 @@ import {
View,
} from 'react-native';
import {
createAppContainer,
Transitioner,
SafeAreaView,
StackRouter,
createNavigationContainer,
createNavigator,
} from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<SafeAreaView forceInset={{ top: 'always' }}>
@@ -100,7 +100,7 @@ const CustomRouter = StackRouter({
Settings: { screen: MySettingsScreen },
});
const CustomTransitioner = createAppContainer(
const CustomTransitioner = createNavigationContainer(
createNavigator(CustomNavigationView, CustomRouter, {})
);

View File

@@ -3,15 +3,14 @@
*/
import React from 'react';
import { Platform, ScrollView, StatusBar } from 'react-native';
import { Button, Platform, ScrollView, StatusBar } from 'react-native';
import {
createStackNavigator,
createDrawerNavigator,
StackNavigator,
DrawerNavigator,
SafeAreaView,
} from 'react-navigation';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
@@ -22,7 +21,7 @@ const MyNavScreen = ({ navigation, banner }) => (
onPress={() => navigation.navigate('Email')}
title="Open other screen"
/>
<Button onPress={() => navigation.navigate('Index')} title="Go back" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
</SafeAreaView>
<StatusBar barStyle="default" />
</ScrollView>
@@ -32,7 +31,14 @@ const InboxScreen = ({ navigation }) => (
<MyNavScreen banner={'Inbox Screen'} navigation={navigation} />
);
InboxScreen.navigationOptions = {
headerTitle: 'Inbox',
drawerLabel: 'Inbox',
drawerIcon: ({ tintColor }) => (
<MaterialIcons
name="move-to-inbox"
size={24}
style={{ color: tintColor }}
/>
),
};
const EmailScreen = ({ navigation }) => (
@@ -43,44 +49,23 @@ const DraftsScreen = ({ navigation }) => (
<MyNavScreen banner={'Drafts Screen'} navigation={navigation} />
);
DraftsScreen.navigationOptions = {
headerTitle: 'Drafts',
drawerLabel: 'Drafts',
drawerIcon: ({ tintColor }) => (
<MaterialIcons name="drafts" size={24} style={{ color: tintColor }} />
),
};
const InboxStack = createStackNavigator(
{
Inbox: { screen: InboxScreen },
Email: { screen: EmailScreen },
},
{
navigationOptions: {
drawerLabel: 'Inbox',
drawerIcon: ({ tintColor }) => (
<MaterialIcons
name="move-to-inbox"
size={24}
style={{ color: tintColor }}
/>
),
},
}
);
const InboxStack = StackNavigator({
Inbox: { screen: InboxScreen },
Email: { screen: EmailScreen },
});
const DraftsStack = createStackNavigator(
{
Drafts: { screen: DraftsScreen },
Email: { screen: EmailScreen },
},
{
navigationOptions: {
drawerLabel: 'Drafts',
drawerIcon: ({ tintColor }) => (
<MaterialIcons name="drafts" size={24} style={{ color: tintColor }} />
),
},
}
);
const DraftsStack = StackNavigator({
Drafts: { screen: DraftsScreen },
Email: { screen: EmailScreen },
});
const DrawerExample = createDrawerNavigator(
const DrawerExample = DrawerNavigator(
{
Inbox: {
path: '/',
@@ -91,7 +76,6 @@ const DrawerExample = createDrawerNavigator(
screen: DraftsStack,
},
},
{
initialRouteName: 'Drafts',
contentOptions: {

View File

@@ -1,96 +0,0 @@
import React from 'react';
import { Button, Text, StatusBar, View, StyleSheet } from 'react-native';
import {
SafeAreaView,
createStackNavigator,
createSwitchNavigator,
NavigationActions,
} from 'react-navigation';
const runSubRoutes = navigation => {
navigation.dispatch(NavigationActions.navigate({ routeName: 'First2' }));
navigation.dispatch(NavigationActions.navigate({ routeName: 'Second2' }));
navigation.dispatch(NavigationActions.navigate({ routeName: 'First2' }));
};
const runSubRoutesWithIntermediate = navigation => {
navigation.dispatch(toFirst1);
navigation.dispatch(toSecond2);
navigation.dispatch(toFirst);
navigation.dispatch(toFirst2);
};
const runSubAction = navigation => {
navigation.dispatch(toFirst2);
navigation.dispatch(toSecond2);
navigation.dispatch(toFirstChild1);
};
const DummyScreen = ({ routeName, navigation, style }) => {
return (
<SafeAreaView
style={[
StyleSheet.absoluteFill,
{
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'white',
},
style,
]}
>
<Text style={{ fontWeight: '800' }}>
{routeName}({navigation.state.key})
</Text>
<View>
<Button title="back" onPress={() => navigation.goBack()} />
<Button title="dismiss" onPress={() => navigation.dismiss()} />
<Button
title="between sub-routes"
onPress={() => runSubRoutes(navigation)}
/>
<Button
title="between sub-routes (with intermediate)"
onPress={() => runSubRoutesWithIntermediate(navigation)}
/>
<Button
title="with sub-action"
onPress={() => runSubAction(navigation)}
/>
</View>
<StatusBar barStyle="default" />
</SafeAreaView>
);
};
const createDummyScreen = routeName => {
const BoundDummyScreen = props => DummyScreen({ ...props, routeName });
return BoundDummyScreen;
};
const toFirst = NavigationActions.navigate({ routeName: 'First' });
const toFirst1 = NavigationActions.navigate({ routeName: 'First1' });
const toFirst2 = NavigationActions.navigate({ routeName: 'First2' });
const toSecond2 = NavigationActions.navigate({ routeName: 'Second2' });
const toFirstChild1 = NavigationActions.navigate({
routeName: 'First',
action: NavigationActions.navigate({ routeName: 'First1' }),
});
export default createStackNavigator(
{
Other: createDummyScreen('Leaf'),
First: createStackNavigator({
First1: createDummyScreen('First1'),
First2: createDummyScreen('First2'),
}),
Second: createStackNavigator({
Second1: createDummyScreen('Second1'),
Second2: createDummyScreen('Second2'),
}),
},
{
headerMode: 'none',
}
);

View File

@@ -1,63 +0,0 @@
import React from 'react';
import { StatusBar, View, TextInput, InteractionManager } from 'react-native';
import { createStackNavigator, withNavigationFocus } from 'react-navigation';
import { Button } from './commonComponents/ButtonWithMargin';
class ScreenOne extends React.Component {
static navigationOptions = {
title: 'Home',
};
render() {
const { navigation } = this.props;
return (
<View style={{ paddingTop: 30 }}>
<Button
onPress={() => navigation.push('ScreenTwo')}
title="Push screen with focused text input"
/>
<Button onPress={() => navigation.goBack(null)} title="Go Home" />
<StatusBar barStyle="default" />
</View>
);
}
}
class ScreenTwo extends React.Component {
static navigationOptions = ({ navigation }) => ({
title: navigation.getParam('inputValue', 'Screen w/ Input'),
});
componentDidMount() {
InteractionManager.runAfterInteractions(() => {
this._textInput.focus();
});
}
render() {
const { navigation } = this.props;
return (
<View style={{ paddingTop: 30 }}>
<View style={{ alignSelf: 'center', paddingVertical: 20 }}>
<TextInput
ref={c => (this._textInput = c)}
onChangeText={inputValue => navigation.setParams({ inputValue })}
style={{
backgroundColor: 'white',
height: 24,
width: 150,
borderColor: '#555',
borderWidth: 1,
}}
/>
</View>
<Button onPress={() => navigation.pop()} title="Pop" />
</View>
);
}
}
export default createStackNavigator({
ScreenOne,
ScreenTwo: withNavigationFocus(ScreenTwo),
});

View File

@@ -3,10 +3,9 @@
*/
import React from 'react';
import { ScrollView, StatusBar, Text } from 'react-native';
import { SafeAreaView, createStackNavigator } from 'react-navigation';
import { Button, ScrollView, StatusBar, Text } from 'react-native';
import { SafeAreaView, StackNavigator } from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
@@ -32,8 +31,7 @@ const MyNavScreen = ({ navigation, banner }) => (
headerVisible:
!navigation.state.params ||
!navigation.state.params.headerVisible,
})
}
})}
/>
)}
<Button onPress={() => navigation.goBack(null)} title="Go back" />
@@ -59,7 +57,7 @@ MyProfileScreen.navigationOptions = ({ navigation }) => ({
title: `${navigation.state.params.name}'s Profile!`,
});
const ProfileNavigator = createStackNavigator(
const ProfileNavigator = StackNavigator(
{
Home: {
screen: MyHomeScreen,
@@ -70,7 +68,7 @@ const ProfileNavigator = createStackNavigator(
},
},
{
defaultNavigationOptions: {
navigationOptions: {
headerLeft: null,
},
mode: 'modal',
@@ -89,7 +87,7 @@ MyHeaderTestScreen.navigationOptions = ({ navigation }) => {
};
};
const ModalStack = createStackNavigator(
const ModalStack = StackNavigator(
{
ProfileNavigator: {
screen: ProfileNavigator,
@@ -97,7 +95,7 @@ const ModalStack = createStackNavigator(
HeaderTest: { screen: MyHeaderTestScreen },
},
{
defaultNavigationOptions: {
navigationOptions: {
header: null,
},
mode: 'modal',

View File

@@ -3,11 +3,10 @@
*/
import React from 'react';
import { Platform, ScrollView, StyleSheet } from 'react-native';
import { createDrawerNavigator } from 'react-navigation';
import { Button, Platform, ScrollView, StyleSheet } from 'react-native';
import { DrawerNavigator } from 'react-navigation';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView style={styles.container}>
@@ -41,7 +40,7 @@ DraftsScreen.navigationOptions = {
),
};
const DrawerExample = createDrawerNavigator(
const DrawerExample = DrawerNavigator(
{
Inbox: {
path: '/',
@@ -60,7 +59,7 @@ const DrawerExample = createDrawerNavigator(
}
);
const MainDrawerExample = createDrawerNavigator({
const MainDrawerExample = DrawerNavigator({
Drafts: {
screen: DrawerExample,
},

View File

@@ -4,44 +4,22 @@
import type {
NavigationScreenProp,
NavigationState,
NavigationStateRoute,
NavigationEventSubscription,
} from 'react-navigation';
import * as React from 'react';
import { Platform, ScrollView, StatusBar } from 'react-native';
import {
createStackNavigator,
SafeAreaView,
withNavigation,
NavigationActions,
StackActions,
} from 'react-navigation';
import invariant from 'invariant';
import { Button, ScrollView, StatusBar } from 'react-native';
import { StackNavigator, SafeAreaView, withNavigation } from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
import { HeaderButtons } from './commonComponents/HeaderButtons';
const DEBUG = false;
type MyNavScreenProps = {
navigation: NavigationScreenProp<NavigationState>,
navigation: NavigationScreenProp<*>,
banner: React.Node,
};
type BackButtonProps = {
navigation: NavigationScreenProp<NavigationStateRoute>,
};
class MyBackButton extends React.Component<BackButtonProps, any> {
class MyBackButton extends React.Component<any, any> {
render() {
return (
<HeaderButtons>
<HeaderButtons.Item title="Back" onPress={this._navigateBack} />
</HeaderButtons>
);
return <Button onPress={this._navigateBack} title="Custom Back" />;
}
_navigateBack = () => {
@@ -54,55 +32,24 @@ const MyBackButtonWithNavigation = withNavigation(MyBackButton);
class MyNavScreen extends React.Component<MyNavScreenProps> {
render() {
const { navigation, banner } = this.props;
const { push, replace, popToTop, pop, dismiss } = navigation;
invariant(
push && replace && popToTop && pop && dismiss,
'missing action creators for StackNavigator'
);
return (
<SafeAreaView forceInset={{ top: 'never' }}>
<SafeAreaView>
<SampleText>{banner}</SampleText>
<Button
onPress={() => push('Profile', { name: 'Jane' })}
onPress={() => navigation.push('Profile', { name: 'Jane' })}
title="Push a profile screen"
/>
<Button
onPress={() =>
navigation.dispatch(
StackActions.reset({
index: 0,
actions: [
NavigationActions.navigate({
routeName: 'Photos',
params: { name: 'Jane' },
}),
],
})
)
}
title="Reset photos"
/>
<Button
onPress={() => navigation.navigate('Photos', { name: 'Jane' })}
title="Navigate to a photos screen"
/>
<Button
onPress={() => replace('Profile', { name: 'Lucy' })}
onPress={() => navigation.replace('Profile', { name: 'Lucy' })}
title="Replace with profile"
/>
<Button onPress={() => popToTop()} title="Pop to top" />
<Button onPress={() => pop()} title="Pop" />
<Button
onPress={() => {
if (navigation.goBack()) {
console.log('goBack handled');
} else {
console.log('goBack unhandled');
}
}}
title="Go back"
/>
<Button onPress={() => dismiss()} title="Dismiss" />
<Button onPress={() => navigation.popToTop()} title="Pop to top" />
<Button onPress={() => navigation.pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
@@ -110,7 +57,7 @@ class MyNavScreen extends React.Component<MyNavScreenProps> {
}
type MyHomeScreenProps = {
navigation: NavigationScreenProp<NavigationState>,
navigation: NavigationScreenProp<*>,
};
class MyHomeScreen extends React.Component<MyHomeScreenProps> {
@@ -135,16 +82,16 @@ class MyHomeScreen extends React.Component<MyHomeScreenProps> {
this._s3.remove();
}
_onWF = a => {
DEBUG && console.log('_willFocus HomeScreen', a);
console.log('_willFocus HomeScreen', a);
};
_onDF = a => {
DEBUG && console.log('_didFocus HomeScreen', a);
console.log('_didFocus HomeScreen', a);
};
_onWB = a => {
DEBUG && console.log('_willBlur HomeScreen', a);
console.log('_willBlur HomeScreen', a);
};
_onDB = a => {
DEBUG && console.log('_didBlur HomeScreen', a);
console.log('_didBlur HomeScreen', a);
};
render() {
@@ -154,7 +101,7 @@ class MyHomeScreen extends React.Component<MyHomeScreenProps> {
}
type MyPhotosScreenProps = {
navigation: NavigationScreenProp<NavigationState>,
navigation: NavigationScreenProp<*>,
};
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
static navigationOptions = {
@@ -179,23 +126,23 @@ class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
this._s3.remove();
}
_onWF = a => {
DEBUG && console.log('_willFocus PhotosScreen', a);
console.log('_willFocus PhotosScreen', a);
};
_onDF = a => {
DEBUG && console.log('_didFocus PhotosScreen', a);
console.log('_didFocus PhotosScreen', a);
};
_onWB = a => {
DEBUG && console.log('_willBlur PhotosScreen', a);
console.log('_willBlur PhotosScreen', a);
};
_onDB = a => {
DEBUG && console.log('_didBlur PhotosScreen', a);
console.log('_didBlur PhotosScreen', a);
};
render() {
const { navigation } = this.props;
return (
<MyNavScreen
banner={`${navigation.getParam('name')}'s Photos`}
banner={`${navigation.state.params.name}'s Photos`}
navigation={navigation}
/>
);
@@ -204,9 +151,9 @@ class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
const MyProfileScreen = ({ navigation }) => (
<MyNavScreen
banner={`${
navigation.getParam('mode') === 'edit' ? 'Now Editing ' : ''
}${navigation.getParam('name')}'s Profile`}
banner={`${navigation.state.params.mode === 'edit' ? 'Now Editing ' : ''}${
navigation.state.params.name
}'s Profile`}
navigation={navigation}
/>
);
@@ -221,35 +168,28 @@ MyProfileScreen.navigationOptions = props => {
// Render a button on the right side of the header.
// When pressed switches the screen to edit mode.
headerRight: (
<HeaderButtons>
<HeaderButtons.Item
title={params.mode === 'edit' ? 'Done' : 'Edit'}
onPress={() =>
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
}
/>
</HeaderButtons>
<Button
title={params.mode === 'edit' ? 'Done' : 'Edit'}
onPress={() =>
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
}
/>
),
};
};
const SimpleStack = createStackNavigator(
{
Home: {
screen: MyHomeScreen,
},
Profile: {
path: 'people/:name',
screen: MyProfileScreen,
},
Photos: {
path: 'photos/:name',
screen: MyPhotosScreen,
},
const SimpleStack = StackNavigator({
Home: {
screen: MyHomeScreen,
},
{
// headerLayoutPreset: 'center',
}
);
Profile: {
path: 'people/:name',
screen: MyProfileScreen,
},
Photos: {
path: 'photos/:name',
screen: MyPhotosScreen,
},
});
export default SimpleStack;

View File

@@ -8,57 +8,25 @@ import type {
} from 'react-navigation';
import React from 'react';
import { Animated, Platform, Text, StatusBar, View } from 'react-native';
import {
ScrollView,
FlatList,
SafeAreaView,
createBottomTabNavigator,
withNavigation,
} from 'react-navigation';
import { Button, Platform, ScrollView, StatusBar, View } from 'react-native';
import { SafeAreaView, TabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const TEXT = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla a hendrerit dui, id consectetur nulla. Curabitur mattis sapien nunc, quis dignissim eros venenatis sit amet. Praesent rutrum dapibus diam quis eleifend. Donec vulputate quis purus sed vulputate. Fusce ipsum felis, cursus at congue vel, consectetur tincidunt purus. Pellentesque et fringilla lorem. In at augue malesuada, sollicitudin ex ut, convallis elit. Curabitur metus nibh, consequat vel libero sit amet, iaculis congue nisl. Maecenas eleifend sodales sapien, fringilla sagittis nisi ornare volutpat. Integer tellus enim, volutpat vitae nisl et, dignissim pharetra leo. Sed sit amet efficitur sapien, at tristique sapien. Aenean dignissim semper sagittis. Nullam sit amet volutpat mi.
Curabitur auctor orci et justo molestie iaculis. Integer elementum tortor ac ipsum egestas pharetra. Etiam ultrices elementum pharetra. Maecenas lobortis ultrices risus dignissim luctus. Nunc malesuada cursus posuere. Vestibulum tristique lectus pretium pellentesque pellentesque. Nunc ac nisi lacus. Duis ultrices dui ac viverra ullamcorper. Morbi placerat laoreet lacus sit amet ullamcorper.
Nulla convallis pulvinar hendrerit. Nulla mattis sem et aliquam ultrices. Nam egestas magna leo, nec luctus turpis sollicitudin ac. Sed id leo luctus, lobortis tortor ut, rhoncus ex. Aliquam gravida enim ac dapibus ultricies. Vestibulum at interdum est, et vehicula nibh. Phasellus dignissim iaculis rhoncus. Vestibulum tempus leo lectus, quis euismod metus ullamcorper quis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut id ipsum at enim eleifend porttitor id quis metus. Proin bibendum ornare iaculis. Duis elementum lacus vel cursus efficitur. Nunc eu tortor sed risus lacinia scelerisque.
Praesent lobortis elit sit amet mauris pulvinar, viverra condimentum massa pellentesque. Curabitur massa ex, dignissim eget neque at, fringilla consectetur justo. Cras sollicitudin vel ligula sed cursus. Aliquam porta sem hendrerit diam porta ultricies. Sed eu mi erat. Curabitur id justo vel tortor hendrerit vestibulum id eget est. Morbi eros magna, placerat id diam ut, varius sollicitudin mi. Curabitur pretium finibus accumsan.`;
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView navigation={navigation} style={{ flex: 1 }}>
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('Home')}
title="Go to home tab"
/>
<Button
onPress={() => navigation.navigate('Settings')}
title="Go to settings tab"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
{TEXT.split('\n').map((p, n) => (
<Text key={n} style={{ marginVertical: 10, marginHorizontal: 8 }}>
{p}
</Text>
))}
<StatusBar barStyle="default" />
</SafeAreaView>
</ScrollView>
);
const MyListScreen = ({ navigation, data }) => (
<FlatList
navigation={navigation}
data={TEXT.split('\n')}
style={{ paddingTop: 10 }}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => (
<Text style={{ fontSize: 16, marginVertical: 10, marginHorizontal: 8 }}>
{item}
</Text>
)}
/>
<SafeAreaView forceInset={{ horizontal: 'always', top: 'always' }}>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('Home')}
title="Go to home tab"
/>
<Button
onPress={() => navigation.navigate('Settings')}
title="Go to settings tab"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
const MyHomeScreen = ({ navigation }) => (
@@ -71,15 +39,14 @@ MyHomeScreen.navigationOptions = {
accessibilityLabel: 'TEST_ID_HOME_ACLBL',
},
tabBarLabel: 'Home',
tabBarIcon: ({ tintColor, focused, horizontal }) => (
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={'ios-home'}
size={horizontal ? 20 : 26}
name={focused ? 'ios-home' : 'ios-home-outline'}
size={26}
style={{ color: tintColor }}
/>
),
};
MyListScreen.navigationOptions = MyHomeScreen.navigationOptions;
type MyPeopleScreenProps = {
navigation: NavigationScreenProp<*>,
@@ -92,25 +59,25 @@ class MyPeopleScreen extends React.Component<MyPeopleScreenProps> {
static navigationOptions = {
tabBarLabel: 'People',
tabBarIcon: ({ tintColor, focused, horizontal }) => (
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={'ios-people'}
size={horizontal ? 20 : 26}
name={focused ? 'ios-people' : 'ios-people-outline'}
size={26}
style={{ color: tintColor }}
/>
),
};
componentDidMount() {
// this._s0 = this.props.navigation.addListener('willFocus', this._onEvent);
// this._s1 = this.props.navigation.addListener('didFocus', this._onEvent);
// this._s2 = this.props.navigation.addListener('willBlur', this._onEvent);
// this._s3 = this.props.navigation.addListener('didBlur', this._onEvent);
this._s0 = this.props.navigation.addListener('willFocus', this._onEvent);
this._s1 = this.props.navigation.addListener('didFocus', this._onEvent);
this._s2 = this.props.navigation.addListener('willBlur', this._onEvent);
this._s3 = this.props.navigation.addListener('didBlur', this._onEvent);
}
componentWillUnmount() {
// this._s0.remove();
// this._s1.remove();
// this._s2.remove();
// this._s3.remove();
this._s0.remove();
this._s1.remove();
this._s2.remove();
this._s3.remove();
}
_onEvent = a => {
console.log('EVENT ON PEOPLE TAB', a.type, a);
@@ -132,25 +99,25 @@ class MyChatScreen extends React.Component<MyChatScreenProps> {
static navigationOptions = {
tabBarLabel: 'Chat',
tabBarIcon: ({ tintColor, focused, horizontal }) => (
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={'ios-chatboxes'}
size={horizontal ? 20 : 26}
name={focused ? 'ios-chatboxes' : 'ios-chatboxes-outline'}
size={26}
style={{ color: tintColor }}
/>
),
};
componentDidMount() {
// this._s0 = this.props.navigation.addListener('willFocus', this._onEvent);
// this._s1 = this.props.navigation.addListener('didFocus', this._onEvent);
// this._s2 = this.props.navigation.addListener('willBlur', this._onEvent);
// this._s3 = this.props.navigation.addListener('didBlur', this._onEvent);
this._s0 = this.props.navigation.addListener('willFocus', this._onEvent);
this._s1 = this.props.navigation.addListener('didFocus', this._onEvent);
this._s2 = this.props.navigation.addListener('willBlur', this._onEvent);
this._s3 = this.props.navigation.addListener('didBlur', this._onEvent);
}
componentWillUnmount() {
// this._s0.remove();
// this._s1.remove();
// this._s2.remove();
// this._s3.remove();
this._s0.remove();
this._s1.remove();
this._s2.remove();
this._s3.remove();
}
_onEvent = a => {
console.log('EVENT ON CHAT TAB', a.type, a);
@@ -167,19 +134,19 @@ const MySettingsScreen = ({ navigation }) => (
MySettingsScreen.navigationOptions = {
tabBarLabel: 'Settings',
tabBarIcon: ({ tintColor, focused, horizontal }) => (
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={'ios-settings'}
size={horizontal ? 20 : 26}
name={focused ? 'ios-settings' : 'ios-settings-outline'}
size={26}
style={{ color: tintColor }}
/>
),
};
const SimpleTabs = createBottomTabNavigator(
const SimpleTabs = TabNavigator(
{
Home: {
screen: MyListScreen,
screen: MyHomeScreen,
path: '',
},
People: {
@@ -196,8 +163,10 @@ const SimpleTabs = createBottomTabNavigator(
},
},
{
lazy: true,
removeClippedSubviews: true,
tabBarOptions: {
activeTintColor: '#e91e63',
activeTintColor: Platform.OS === 'ios' ? '#e91e63' : '#fff',
},
}
);
@@ -214,16 +183,16 @@ class SimpleTabsContainer extends React.Component<SimpleTabsContainerProps> {
_s3: NavigationEventSubscription;
componentDidMount() {
// this._s0 = this.props.navigation.addListener('willFocus', this._onAction);
// this._s1 = this.props.navigation.addListener('didFocus', this._onAction);
// this._s2 = this.props.navigation.addListener('willBlur', this._onAction);
// this._s3 = this.props.navigation.addListener('didBlur', this._onAction);
this._s0 = this.props.navigation.addListener('willFocus', this._onAction);
this._s1 = this.props.navigation.addListener('didFocus', this._onAction);
this._s2 = this.props.navigation.addListener('willBlur', this._onAction);
this._s3 = this.props.navigation.addListener('didBlur', this._onAction);
}
componentWillUnmount() {
// this._s0.remove();
// this._s1.remove();
// this._s2.remove();
// this._s3.remove();
this._s0.remove();
this._s1.remove();
this._s2.remove();
this._s3.remove();
}
_onAction = a => {
console.log('TABS EVENT', a.type, a);

View File

@@ -1,145 +0,0 @@
/**
* @flow
*/
import type { NavigationScreenProp } from 'react-navigation';
import * as React from 'react';
import { Image, Button, StatusBar, StyleSheet } from 'react-native';
import { createStackNavigator, SafeAreaView } from 'react-navigation';
import SampleText from './SampleText';
type MyNavScreenProps = {
navigation: NavigationScreenProp<*>,
banner: React.Node,
};
class MyCustomHeaderBackImage extends React.Component<any, any> {
render() {
const source = require('./assets/back.png');
return (
<Image
source={source}
style={[styles.myCustomHeaderBackImage, this.props.style]}
/>
);
}
}
class MyNavScreen extends React.Component<MyNavScreenProps> {
render() {
const { navigation, banner } = this.props;
return (
<SafeAreaView>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('Photos', { name: 'Jane' })}
title="Navigate to a photos screen"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
type MyHomeScreenProps = {
navigation: NavigationScreenProp<*>,
};
class MyHomeScreen extends React.Component<MyHomeScreenProps> {
static navigationOptions = {
title: 'Welcome',
headerBackTitle: null,
};
render() {
const { navigation } = this.props;
return <MyNavScreen banner="Home Screen" navigation={navigation} />;
}
}
type MyPhotosScreenProps = {
navigation: NavigationScreenProp<*>,
};
class MyPhotosScreen extends React.Component<MyPhotosScreenProps> {
static navigationOptions = ({ navigation }) => ({
title: `${navigation.state.params.name}'s photos`,
headerBackTitle: null,
});
render() {
const { navigation } = this.props;
return (
<SafeAreaView>
<SampleText>{`${navigation.state.params.name}'s Photos`}</SampleText>
<Button
onPress={() => navigation.navigate('Profile', { name: 'Jane' })}
title="Navigate to a profile screen"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
type MyProfileScreenProps = {
navigation: NavigationScreenProp<*>,
};
class MyProfileScreen extends React.Component<MyProfileScreenProps> {
static navigationOptions = ({ navigation }) => ({
title: 'Profile',
headerBackImage: (
<MyCustomHeaderBackImage style={styles.myCustomHeaderBackImageAlt} />
),
});
render() {
const { navigation } = this.props;
return (
<SafeAreaView>
<SampleText>{`${navigation.state.params.name}'s Profile`}</SampleText>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
const StackWithCustomHeaderBackImage = createStackNavigator(
{
Home: {
screen: MyHomeScreen,
},
Photos: {
path: 'photos/:name',
screen: MyPhotosScreen,
},
Profile: {
path: 'profile/:name',
screen: MyProfileScreen,
},
},
{
defaultNavigationOptions: {
headerBackImage: MyCustomHeaderBackImage,
},
}
);
export default StackWithCustomHeaderBackImage;
const styles = StyleSheet.create({
myCustomHeaderBackImage: {
height: 14.5,
width: 24,
marginLeft: 9,
marginRight: 12,
marginVertical: 12,
resizeMode: 'contain',
},
myCustomHeaderBackImageAlt: {
tintColor: '#f00',
},
});

View File

@@ -4,11 +4,8 @@
import type { NavigationScreenProp } from 'react-navigation';
import * as React from 'react';
import { ScrollView, StatusBar } from 'react-native';
import { createStackNavigator, SafeAreaView } from 'react-navigation';
import invariant from 'invariant';
import { Button } from './commonComponents/ButtonWithMargin';
import { Button, ScrollView, StatusBar } from 'react-native';
import { StackNavigator, SafeAreaView } from 'react-navigation';
type NavScreenProps = {
navigation: NavigationScreenProp<*>,
@@ -21,17 +18,15 @@ class HomeScreen extends React.Component<NavScreenProps> {
render() {
const { navigation } = this.props;
const { push } = navigation;
invariant(push, 'missing `push` action creator for StackNavigator');
return (
<SafeAreaView style={{ paddingTop: 30 }}>
<Button onPress={() => push('Other')} title="Push another screen" />
<Button
onPress={() => push('ScreenWithNoHeader')}
title="Push screen with no header"
onPress={() => navigation.push('Other')}
title="Push another screen"
/>
<Button onPress={() => navigation.goBack(null)} title="Go Home" />
<Button onPress={() => navigation.pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
@@ -45,20 +40,14 @@ class OtherScreen extends React.Component<NavScreenProps> {
render() {
const { navigation } = this.props;
const { push, pop } = navigation;
invariant(push && pop, 'missing action creators for StackNavigator');
return (
<SafeAreaView style={{ paddingTop: 30 }}>
<Button
onPress={() => push('ScreenWithLongTitle')}
onPress={() => navigation.push('Other')}
title="Push another screen"
/>
<Button
onPress={() => push('ScreenWithNoHeader')}
title="Push screen with no header"
/>
<Button onPress={() => pop()} title="Pop" />
<Button onPress={() => navigation.pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
@@ -66,54 +55,10 @@ class OtherScreen extends React.Component<NavScreenProps> {
}
}
class ScreenWithLongTitle extends React.Component<NavScreenProps> {
static navigationOptions = {
title: "Another title that's kind of long",
};
render() {
const { navigation } = this.props;
const { pop } = navigation;
invariant(pop, 'missing `pop` action creator for StackNavigator');
return (
<SafeAreaView style={{ paddingTop: 30 }}>
<Button onPress={() => pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
class ScreenWithNoHeader extends React.Component<NavScreenProps> {
static navigationOptions = {
header: null,
title: 'No Header',
};
render() {
const { navigation } = this.props;
const { push, pop } = navigation;
invariant(push && pop, 'missing action creators for StackNavigator');
return (
<SafeAreaView style={{ paddingTop: 30 }}>
<Button onPress={() => push('Other')} title="Push another screen" />
<Button onPress={() => pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
const StackWithHeaderPreset = createStackNavigator(
const StackWithHeaderPreset = StackNavigator(
{
Home: HomeScreen,
Other: OtherScreen,
ScreenWithNoHeader: ScreenWithNoHeader,
ScreenWithLongTitle: ScreenWithLongTitle,
},
{
headerTransitionPreset: 'uikit',

View File

@@ -12,23 +12,15 @@ import { isIphoneX } from 'react-native-iphone-x-helper';
import * as React from 'react';
import { BlurView, Constants } from 'expo';
import {
Button,
Dimensions,
Platform,
ScrollView,
StatusBar,
StyleSheet,
View,
} from 'react-native';
import {
Header,
HeaderStyleInterpolator,
createStackNavigator,
} from 'react-navigation';
import invariant from 'invariant';
import { Header, StackNavigator } from 'react-navigation';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
import { HeaderButtons } from './commonComponents/HeaderButtons';
type MyNavScreenProps = {
navigation: NavigationScreenProp<*>,
@@ -38,16 +30,11 @@ type MyNavScreenProps = {
class MyNavScreen extends React.Component<MyNavScreenProps> {
render() {
const { navigation, banner } = this.props;
const { push, replace, popToTop, pop } = navigation;
invariant(
push && replace && popToTop && pop,
'missing action creators for StackNavigator'
);
return (
<ScrollView style={{ flex: 1 }} {...this.getHeaderInset()}>
<SampleText>{banner}</SampleText>
<Button
onPress={() => push('Profile', { name: 'Jane' })}
onPress={() => navigation.push('Profile', { name: 'Jane' })}
title="Push a profile screen"
/>
<Button
@@ -55,11 +42,11 @@ class MyNavScreen extends React.Component<MyNavScreenProps> {
title="Navigate to a photos screen"
/>
<Button
onPress={() => replace('Profile', { name: 'Lucy' })}
onPress={() => navigation.replace('Profile', { name: 'Lucy' })}
title="Replace with profile"
/>
<Button onPress={() => popToTop()} title="Pop to top" />
<Button onPress={() => pop()} title="Pop" />
<Button onPress={() => navigation.popToTop()} title="Pop to top" />
<Button onPress={() => navigation.pop()} title="Pop" />
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</ScrollView>
@@ -206,19 +193,17 @@ MyProfileScreen.navigationOptions = props => {
// Render a button on the right side of the header.
// When pressed switches the screen to edit mode.
headerRight: (
<HeaderButtons>
<HeaderButtons.Item
title={params.mode === 'edit' ? 'Done' : 'Edit'}
onPress={() =>
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
}
/>
</HeaderButtons>
<Button
title={params.mode === 'edit' ? 'Done' : 'Edit'}
onPress={() =>
setParams({ mode: params.mode === 'edit' ? '' : 'edit' })
}
/>
),
};
};
const StackWithTranslucentHeader = createStackNavigator(
const StackWithTranslucentHeader = StackNavigator(
{
Home: {
screen: MyHomeScreen,
@@ -234,18 +219,8 @@ const StackWithTranslucentHeader = createStackNavigator(
},
{
headerTransitionPreset: 'uikit',
// You can leave this out if you don't want the card shadow to
// be visible through the header
transitionConfig: () => ({
headerBackgroundInterpolator:
HeaderStyleInterpolator.forBackgroundWithTranslation,
}),
defaultNavigationOptions: {
navigationOptions: {
headerTransparent: true,
headerStyle: {
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#A7A7AA',
},
headerBackground: Platform.select({
ios: <BlurView style={{ flex: 1 }} intensity={98} />,
android: (

View File

@@ -3,59 +3,38 @@
*/
import React from 'react';
import { StatusBar, Text } from 'react-native';
import {
ScrollView,
SafeAreaView,
createStackNavigator,
createBottomTabNavigator,
withNavigation,
} from 'react-navigation';
import { Button, ScrollView, StatusBar } from 'react-native';
import { SafeAreaView, StackNavigator, TabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const TEXT = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla a hendrerit dui, id consectetur nulla. Curabitur mattis sapien nunc, quis dignissim eros venenatis sit amet. Praesent rutrum dapibus diam quis eleifend. Donec vulputate quis purus sed vulputate. Fusce ipsum felis, cursus at congue vel, consectetur tincidunt purus. Pellentesque et fringilla lorem. In at augue malesuada, sollicitudin ex ut, convallis elit. Curabitur metus nibh, consequat vel libero sit amet, iaculis congue nisl. Maecenas eleifend sodales sapien, fringilla sagittis nisi ornare volutpat. Integer tellus enim, volutpat vitae nisl et, dignissim pharetra leo. Sed sit amet efficitur sapien, at tristique sapien. Aenean dignissim semper sagittis. Nullam sit amet volutpat mi.
Curabitur auctor orci et justo molestie iaculis. Integer elementum tortor ac ipsum egestas pharetra. Etiam ultrices elementum pharetra. Maecenas lobortis ultrices risus dignissim luctus. Nunc malesuada cursus posuere. Vestibulum tristique lectus pretium pellentesque pellentesque. Nunc ac nisi lacus. Duis ultrices dui ac viverra ullamcorper. Morbi placerat laoreet lacus sit amet ullamcorper.
Nulla convallis pulvinar hendrerit. Nulla mattis sem et aliquam ultrices. Nam egestas magna leo, nec luctus turpis sollicitudin ac. Sed id leo luctus, lobortis tortor ut, rhoncus ex. Aliquam gravida enim ac dapibus ultricies. Vestibulum at interdum est, et vehicula nibh. Phasellus dignissim iaculis rhoncus. Vestibulum tempus leo lectus, quis euismod metus ullamcorper quis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut id ipsum at enim eleifend porttitor id quis metus. Proin bibendum ornare iaculis. Duis elementum lacus vel cursus efficitur. Nunc eu tortor sed risus lacinia scelerisque.
Praesent lobortis elit sit amet mauris pulvinar, viverra condimentum massa pellentesque. Curabitur massa ex, dignissim eget neque at, fringilla consectetur justo. Cras sollicitudin vel ligula sed cursus. Aliquam porta sem hendrerit diam porta ultricies. Sed eu mi erat. Curabitur id justo vel tortor hendrerit vestibulum id eget est. Morbi eros magna, placerat id diam ut, varius sollicitudin mi. Curabitur pretium finibus accumsan.`;
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
<SafeAreaView forceInset={{ horizontal: 'always' }}>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
title="Open profile screen"
/>
<Button
onPress={() => navigation.navigate('NotifSettings')}
title="Open notifications screen"
/>
<Button
onPress={() => navigation.navigate('SettingsTab')}
title="Go to settings tab"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
</SafeAreaView>
class MyNavScreen extends React.Component {
render() {
const { navigation } = this.props;
const banner = navigation.getParam('banner');
<StatusBar barStyle="default" />
</ScrollView>
);
return (
<ScrollView style={{ flex: 1 }}>
<SafeAreaView forceInset={{ horizontal: 'always' }}>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
title="Open profile screen"
/>
<Button
onPress={() => navigation.navigate('NotifSettings')}
title="Open notifications screen"
/>
<Button
onPress={() => navigation.navigate('SettingsTab')}
title="Go to settings tab"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
{TEXT.split('\n').map((p, n) => (
<Text key={n} style={{ marginVertical: 10, marginHorizontal: 8 }}>
{p}
</Text>
))}
</SafeAreaView>
<StatusBar barStyle="default" />
</ScrollView>
);
}
}
const MyHomeScreen = ({ navigation }) => (
<MyNavScreen banner="Home Screen" navigation={navigation} />
);
const MyProfileScreen = ({ navigation }) => (
<MyNavScreen
@@ -64,11 +43,18 @@ const MyProfileScreen = ({ navigation }) => (
/>
);
const MainTab = createStackNavigator({
const MyNotificationsSettingsScreen = ({ navigation }) => (
<MyNavScreen banner="Notifications Screen" navigation={navigation} />
);
const MySettingsScreen = ({ navigation }) => (
<MyNavScreen banner="Settings Screen" navigation={navigation} />
);
const MainTab = StackNavigator({
Home: {
screen: MyNavScreen,
screen: MyHomeScreen,
path: '/',
params: { banner: 'Home Screen' },
navigationOptions: {
title: 'Welcome',
},
@@ -82,25 +68,23 @@ const MainTab = createStackNavigator({
},
});
const SettingsTab = createStackNavigator({
const SettingsTab = StackNavigator({
Settings: {
screen: MyNavScreen,
screen: MySettingsScreen,
path: '/',
params: { banner: 'Settings Screen' },
navigationOptions: () => ({
title: 'Settings',
}),
},
NotifSettings: {
screen: MyNavScreen,
params: { banner: 'Notifications Screen' },
screen: MyNotificationsSettingsScreen,
navigationOptions: {
title: 'Notifications',
},
},
});
const StacksInTabs = createBottomTabNavigator(
const StacksInTabs = TabNavigator(
{
MainTab: {
screen: MainTab,
@@ -109,7 +93,7 @@ const StacksInTabs = createBottomTabNavigator(
tabBarLabel: 'Home',
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-home' : 'ios-home'}
name={focused ? 'ios-home' : 'ios-home-outline'}
size={26}
style={{ color: tintColor }}
/>
@@ -123,7 +107,7 @@ const StacksInTabs = createBottomTabNavigator(
tabBarLabel: 'Settings',
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-settings' : 'ios-settings'}
name={focused ? 'ios-settings' : 'ios-settings-outline'}
size={26}
style={{ color: tintColor }}
/>
@@ -132,9 +116,9 @@ const StacksInTabs = createBottomTabNavigator(
},
},
{
tabBarOptions: {
showLabel: false,
},
tabBarPosition: 'bottom',
animationEnabled: false,
swipeEnabled: false,
}
);

View File

@@ -3,21 +3,15 @@
*/
import React from 'react';
import { ScrollView, StatusBar } from 'react-native';
import {
SafeAreaView,
createStackNavigator,
createBottomTabNavigator,
getActiveChildNavigationOptions,
} from 'react-navigation';
import { Button, ScrollView, StatusBar } from 'react-native';
import { SafeAreaView, StackNavigator, TabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
<SafeAreaView forceInset={{ horizontal: 'always', vertical: 'never' }}>
<SafeAreaView forceInset={{ horizontal: 'always' }}>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
@@ -56,7 +50,7 @@ const MySettingsScreen = ({ navigation }) => (
<MyNavScreen banner="Settings Screen" navigation={navigation} />
);
const TabNav = createBottomTabNavigator(
const TabNav = TabNavigator(
{
MainTab: {
screen: MyHomeScreen,
@@ -66,7 +60,7 @@ const TabNav = createBottomTabNavigator(
tabBarLabel: 'Home',
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-home' : 'ios-home'}
name={focused ? 'ios-home' : 'ios-home-outline'}
size={26}
style={{ color: tintColor }}
/>
@@ -80,7 +74,7 @@ const TabNav = createBottomTabNavigator(
title: 'Settings',
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-settings' : 'ios-settings'}
name={focused ? 'ios-settings' : 'ios-settings-outline'}
size={26}
style={{ color: tintColor }}
/>
@@ -95,14 +89,7 @@ const TabNav = createBottomTabNavigator(
}
);
TabNav.navigationOptions = ({ navigation, screenProps }) => {
const childOptions = getActiveChildNavigationOptions(navigation, screenProps);
return {
title: childOptions.title,
};
};
const StacksOverTabs = createStackNavigator({
const StacksOverTabs = StackNavigator({
Root: {
screen: TabNav,
},
@@ -115,9 +102,9 @@ const StacksOverTabs = createStackNavigator({
Profile: {
screen: MyProfileScreen,
path: '/people/:name',
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.name}'s Profile!`,
}),
navigationOptions: ({ navigation }) => {
title: `${navigation.state.params.name}'s Profile!`;
},
},
});

View File

@@ -1,142 +0,0 @@
/**
* @flow
*/
import React from 'react';
import { View, ScrollView, StatusBar, StyleSheet } from 'react-native';
import {
SafeAreaView,
createStackNavigator,
createMaterialTopTabNavigator,
} from 'react-navigation';
import { Constants } from 'expo';
import { MaterialTopTabBar } from 'react-navigation-tabs';
import SampleText from './SampleText';
import { Button } from './commonComponents/ButtonWithMargin';
const HEADER_HEIGHT = 64;
const MyNavScreen = ({ navigation, banner, statusBarStyle }) => (
<ScrollView>
<SafeAreaView forceInset={{ horizontal: 'always' }}>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
title="Open profile screen"
/>
<Button
onPress={() => navigation.navigate('NotifSettings')}
title="Open notifications screen"
/>
<Button
onPress={() => navigation.navigate('SettingsTab')}
title="Go to settings tab"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
</SafeAreaView>
<StatusBar barStyle={statusBarStyle || 'default'} />
</ScrollView>
);
const MyHomeScreen = ({ navigation }) => (
<MyNavScreen
banner="Home Screen"
navigation={navigation}
statusBarStyle="light-content"
/>
);
const MyProfileScreen = ({ navigation }) => (
<MyNavScreen
banner={`${navigation.state.params.name}s Profile`}
navigation={navigation}
/>
);
const MyNotificationsSettingsScreen = ({ navigation }) => (
<MyNavScreen banner="Notifications Screen" navigation={navigation} />
);
const MySettingsScreen = ({ navigation }) => (
<MyNavScreen
banner="Settings Screen"
navigation={navigation}
statusBarStyle="light-content"
/>
);
const styles = StyleSheet.create({
stackHeader: {
height: HEADER_HEIGHT,
},
tab: {
height: HEADER_HEIGHT,
},
});
function MaterialTopTabBarWithStatusBar(props) {
return (
<View
style={{
paddingTop: Constants.statusBarHeight,
backgroundColor: '#2196f3',
}}
>
<MaterialTopTabBar {...props} jumpToIndex={() => {}} />
</View>
);
}
const TabNavigator = createMaterialTopTabNavigator(
{
MainTab: {
screen: MyHomeScreen,
navigationOptions: {
title: 'Welcome',
},
},
SettingsTab: {
screen: MySettingsScreen,
navigationOptions: {
title: 'Settings',
},
},
},
{
tabBarComponent: MaterialTopTabBarWithStatusBar,
tabBarOptions: {
tabStyle: styles.tab,
},
}
);
const StackNavigator = createStackNavigator(
{
Root: {
screen: TabNavigator,
navigationOptions: {
header: null,
},
},
NotifSettings: {
screen: MyNotificationsSettingsScreen,
navigationOptions: {
title: 'Notifications',
},
},
Profile: {
screen: MyProfileScreen,
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.name}'s Profile!`,
}),
},
},
{
defaultNavigationOptions: {
headerStyle: styles.stackHeader,
},
}
);
export default StackNavigator;

View File

@@ -1,7 +1,6 @@
import React from 'react';
import { StatusBar, Text, View } from 'react-native';
import { createStackNavigator } from 'react-navigation';
import { Button } from './commonComponents/ButtonWithMargin';
import { Button, StatusBar, Text, View } from 'react-native';
import { StackNavigator } from 'react-navigation';
class HomeScreen extends React.Component<any, any> {
render() {
@@ -82,7 +81,7 @@ class SettingsScreen extends React.Component<any, any> {
}
}
const Stack = createStackNavigator(
const Stack = StackNavigator(
{
Home: {
screen: HomeScreen,

View File

@@ -1,121 +0,0 @@
/**
* @flow
*/
import React from 'react';
import {
ActivityIndicator,
AsyncStorage,
StatusBar,
StyleSheet,
View,
} from 'react-native';
import { createStackNavigator, createSwitchNavigator } from 'react-navigation';
import { Button } from './commonComponents/ButtonWithMargin';
class SignInScreen extends React.Component<any, any> {
static navigationOptions = {
title: 'Please sign in',
};
render() {
return (
<View style={styles.container}>
<Button title="Sign in!" onPress={this._signInAsync} />
<Button
title="Go back to other examples"
onPress={() => this.props.navigation.goBack(null)}
/>
<StatusBar barStyle="default" />
</View>
);
}
_signInAsync = async () => {
await AsyncStorage.setItem('userToken', 'abc');
this.props.navigation.navigate('Home');
};
}
class HomeScreen extends React.Component<any, any> {
static navigationOptions = {
title: 'Welcome to the app!',
};
render() {
return (
<View style={styles.container}>
<Button title="Show me more of the app" onPress={this._showMoreApp} />
<Button title="Actually, sign me out :)" onPress={this._signOutAsync} />
<StatusBar barStyle="default" />
</View>
);
}
_showMoreApp = () => {
this.props.navigation.navigate('Other');
};
_signOutAsync = async () => {
await AsyncStorage.clear();
this.props.navigation.navigate('Auth');
};
}
class OtherScreen extends React.Component<any, any> {
static navigationOptions = {
title: 'Lots of features here',
};
render() {
return (
<View style={styles.container}>
<Button title="I'm done, sign me out" onPress={this._signOutAsync} />
<StatusBar barStyle="default" />
</View>
);
}
_signOutAsync = async () => {
await AsyncStorage.clear();
this.props.navigation.navigate('Auth');
};
}
class LoadingScreen extends React.Component<any, any> {
componentDidMount() {
this._bootstrapAsync();
}
_bootstrapAsync = async () => {
const userToken = await AsyncStorage.getItem('userToken');
let initialRouteName = userToken ? 'App' : 'Auth';
this.props.navigation.navigate(initialRouteName);
};
render() {
return (
<View style={styles.container}>
<ActivityIndicator />
<StatusBar barStyle="default" />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
});
const AppStack = createStackNavigator({ Home: HomeScreen, Other: OtherScreen });
const AuthStack = createStackNavigator({ SignIn: SignInScreen });
export default createSwitchNavigator({
Loading: LoadingScreen,
App: AppStack,
Auth: AuthStack,
});

View File

@@ -0,0 +1,127 @@
/**
* @flow
*/
import React from 'react';
import { Animated, Button, ScrollView, StatusBar } from 'react-native';
import { StackNavigator, TabNavigator } from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
import SampleText from './SampleText';
const MyNavScreen = ({ navigation, banner }) => (
<ScrollView>
<SampleText>{banner}</SampleText>
<Button
onPress={() => navigation.navigate('Profile', { name: 'Jordan' })}
title="Open profile screen"
/>
<Button
onPress={() => navigation.navigate('NotifSettings')}
title="Open notifications screen"
/>
<Button
onPress={() => navigation.navigate('SettingsTab')}
title="Go to settings tab"
/>
<Button onPress={() => navigation.goBack(null)} title="Go back" />
<StatusBar barStyle="default" />
</ScrollView>
);
const MyHomeScreen = ({ navigation }) => (
<MyNavScreen banner="Home Screen" navigation={navigation} />
);
const MyProfileScreen = ({ navigation }) => (
<MyNavScreen
banner={`${navigation.state.params.name}s Profile`}
navigation={navigation}
/>
);
const MyNotificationsSettingsScreen = ({ navigation }) => (
<MyNavScreen banner="Notifications Screen" navigation={navigation} />
);
const MySettingsScreen = ({ navigation }) => (
<MyNavScreen banner="Settings Screen" navigation={navigation} />
);
const MainTab = StackNavigator({
Home: {
screen: MyHomeScreen,
path: '/',
navigationOptions: {
title: 'Welcome',
},
},
Profile: {
screen: MyProfileScreen,
path: '/people/:name',
navigationOptions: ({ navigation }) => ({
title: `${navigation.state.params.name}'s Profile!`,
}),
},
});
const SettingsTab = StackNavigator({
Settings: {
screen: MySettingsScreen,
path: '/',
navigationOptions: () => ({
title: 'Settings',
}),
},
NotifSettings: {
screen: MyNotificationsSettingsScreen,
navigationOptions: {
title: 'Notifications',
},
},
});
const TabAnimations = TabNavigator(
{
MainTab: {
screen: MainTab,
path: '/',
navigationOptions: {
tabBarLabel: 'Home',
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-home' : 'ios-home-outline'}
size={26}
style={{ color: tintColor }}
/>
),
},
},
SettingsTab: {
screen: SettingsTab,
path: '/settings',
navigationOptions: {
tabBarLabel: 'Settings',
tabBarIcon: ({ tintColor, focused }) => (
<Ionicons
name={focused ? 'ios-settings' : 'ios-settings-outline'}
size={26}
style={{ color: tintColor }}
/>
),
},
},
},
{
tabBarPosition: 'bottom',
animationEnabled: true,
configureTransition: (currentTransitionProps,nextTransitionProps) => ({
timing: Animated.spring,
tension: 1,
friction: 35,
}),
swipeEnabled: false,
}
);
export default TabAnimations;

View File

@@ -3,29 +3,41 @@
*/
import React from 'react';
import { Platform, ScrollView } from 'react-native';
import { createDrawerNavigator } from 'react-navigation';
import { Button, Platform, ScrollView } from 'react-native';
import { TabNavigator, DrawerNavigator } from 'react-navigation';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import SimpleTabs from './SimpleTabs';
import StacksOverTabs from './StacksOverTabs';
const TabsInDrawer = createDrawerNavigator({
const TabsInDrawer = DrawerNavigator({
SimpleTabs: {
screen: SimpleTabs,
navigationOptions: {
drawerLabel: 'Simple tabs',
drawerIcon: ({ tintColor }) => (
<MaterialIcons name="filter-1" size={24} style={{ color: tintColor }} />
),
drawer: () => ({
label: 'Simple Tabs',
icon: ({ tintColor }) => (
<MaterialIcons
name="filter-1"
size={24}
style={{ color: tintColor }}
/>
),
}),
},
},
StacksOverTabs: {
screen: StacksOverTabs,
navigationOptions: {
drawerLabel: 'Stacks Over Tabs',
drawerIcon: ({ tintColor }) => (
<MaterialIcons name="filter-2" size={24} style={{ color: tintColor }} />
),
drawer: () => ({
label: 'Stacks Over Tabs',
icon: ({ tintColor }) => (
<MaterialIcons
name="filter-2"
size={24}
style={{ color: tintColor }}
/>
),
}),
},
},
});

View File

@@ -1,127 +0,0 @@
/**
* @flow
*/
import React from 'react';
import { FlatList, SafeAreaView, StatusBar, Text, View } from 'react-native';
import { NavigationEvents } from 'react-navigation';
import { createMaterialBottomTabNavigator } from 'react-navigation-material-bottom-tabs';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
const Event = ({ event }) => (
<View
style={{
borderColor: 'grey',
borderWidth: 1,
borderRadius: 3,
padding: 5,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<Text>{event.type}</Text>
<Text>
{event.action.type.replace('Navigation/', '')}
{event.action.routeName ? '=>' + event.action.routeName : ''}
</Text>
</View>
);
const createTabScreen = (name, icon, focusedIcon) => {
class TabScreen extends React.Component<any, any> {
static navigationOptions = {
tabBarLabel: name,
tabBarIcon: ({ tintColor, focused }) => (
<MaterialCommunityIcons
name={focused ? focusedIcon : icon}
size={26}
style={{ color: focused ? tintColor : '#ccc' }}
/>
),
};
state = { eventLog: [] };
append = navigationEvent => {
this.setState(({ eventLog }) => ({
eventLog: eventLog.concat(navigationEvent),
}));
};
render() {
return (
<SafeAreaView
forceInset={{ horizontal: 'always', top: 'always' }}
style={{
flex: 1,
}}
>
<Text
style={{
margin: 10,
marginTop: 30,
fontSize: 30,
fontWeight: 'bold',
}}
>
Events for tab {name}
</Text>
<View style={{ flex: 1, width: '100%', marginTop: 10 }}>
<FlatList
data={this.state.eventLog}
keyExtractor={item => `${this.state.eventLog.indexOf(item)}`}
renderItem={({ item }) => (
<View
style={{
marginVertical: 5,
marginHorizontal: 10,
backgroundColor: '#e4e4e4',
}}
>
<Event event={item} />
</View>
)}
/>
</View>
<NavigationEvents
onWillFocus={this.append}
onDidFocus={this.append}
onWillBlur={this.append}
onDidBlur={this.append}
/>
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
}
return TabScreen;
};
const TabsWithNavigationEvents = createMaterialBottomTabNavigator(
{
One: {
screen: createTabScreen('One', 'numeric-1-box', 'numeric-1-box'),
},
Two: {
screen: createTabScreen('Two', 'numeric-2-box', 'numeric-2-box'),
},
Three: {
screen: createTabScreen(
'Three',
'numeric-3-box',
'numeric-3-box'
),
},
},
{
shifting: false,
activeTintColor: '#F44336',
}
);
export default TabsWithNavigationEvents;

View File

@@ -3,11 +3,9 @@
*/
import React from 'react';
import { SafeAreaView, StatusBar, Text, View } from 'react-native';
import { withNavigationFocus } from 'react-navigation';
import { createMaterialBottomTabNavigator } from 'react-navigation-material-bottom-tabs';
import { Button, SafeAreaView, Text } from 'react-native';
import { TabNavigator, withNavigationFocus } from 'react-navigation';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { Button } from './commonComponents/ButtonWithMargin';
import SampleText from './SampleText';
@@ -67,10 +65,9 @@ const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
/>
)}
<Button
onPress={() => this.props.navigation.pop()}
onPress={() => this.props.navigation.goBack(null)}
title="Back to other examples"
/>
<StatusBar barStyle="default" />
</SafeAreaView>
);
}
@@ -78,25 +75,26 @@ const createTabScreen = (name, icon, focusedIcon, tintColor = '#673ab7') => {
return withNavigationFocus(TabScreen);
};
const TabsWithNavigationFocus = createMaterialBottomTabNavigator(
const TabsWithNavigationFocus = TabNavigator(
{
One: {
screen: createTabScreen('One', 'numeric-1-box', 'numeric-1-box'),
screen: createTabScreen('One', 'numeric-1-box-outline', 'numeric-1-box'),
},
Two: {
screen: createTabScreen('Two', 'numeric-2-box', 'numeric-2-box'),
screen: createTabScreen('Two', 'numeric-2-box-outline', 'numeric-2-box'),
},
Three: {
screen: createTabScreen(
'Three',
'numeric-3-box',
'numeric-3-box-outline',
'numeric-3-box'
),
},
},
{
shifting: false,
activeTintColor: '#F44336',
tabBarPosition: 'bottom',
animationEnabled: true,
swipeEnabled: true,
}
);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -1,165 +0,0 @@
import React from 'react';
import { Platform, StyleSheet, Text, View } from 'react-native';
import { BorderlessButton, RectButton } from 'react-native-gesture-handler';
const invariant = require('fbjs/lib/invariant');
type ButtonProps = $ReadOnly<{|
/**
* Text to display inside the button
*/
title: string,
/**
* Handler to be called when the user taps the button
*/
onPress: (event?: any) => mixed,
/**
* Color of the text (iOS), or background color of the button (Android)
*/
color?: ?string,
/**
* TV preferred focus (see documentation for the View component).
*/
hasTVPreferredFocus?: ?boolean,
/**
* Text to display for blindness accessibility features
*/
accessibilityLabel?: ?string,
/**
* If true, disable all interactions for this component.
*/
disabled?: ?boolean,
/**
* Used to locate this view in end-to-end tests.
*/
testID?: ?string,
|}>;
/**
* A basic button component that should render nicely on any platform. Supports
* a minimal level of customization.
*
* <center><img src="img/buttonExample.png"></img></center>
*
* If this button doesn't look right for your app, you can build your own
* button using [TouchableOpacity](docs/touchableopacity.html)
* or [TouchableNativeFeedback](docs/touchablenativefeedback.html).
* For inspiration, look at the [source code for this button component](https://github.com/facebook/react-native/blob/master/Libraries/Components/Button.js).
* Or, take a look at the [wide variety of button components built by the community](https://js.coach/react-native?search=button).
*
* Example usage:
*
* ```
* import { Button } from 'react-native';
* ...
*
* <Button
* onPress={onPressLearnMore}
* title="Learn More"
* color="#841584"
* accessibilityLabel="Learn more about this purple button"
* />
* ```
*
*/
export default class Button extends React.Component<ButtonProps> {
render() {
const {
accessibilityLabel,
color,
onPress,
title,
hasTVPreferredFocus,
disabled,
testID,
} = this.props;
const buttonStyles = [styles.button];
const textStyles = [styles.text];
if (color) {
if (Platform.OS === 'ios') {
textStyles.push({ color: color });
} else {
buttonStyles.push({ backgroundColor: color });
}
}
const accessibilityStates = [];
if (disabled) {
buttonStyles.push(styles.buttonDisabled);
textStyles.push(styles.textDisabled);
accessibilityStates.push('disabled');
}
invariant(
typeof title === 'string',
'The title prop of a Button must be a string'
);
const formattedTitle =
Platform.OS === 'android' ? title.toUpperCase() : title;
const Touchable = Platform.OS === 'android' ? RectButton : BorderlessButton;
return (
<Touchable
accessibilityLabel={accessibilityLabel}
accessibilityRole="button"
accessibilityStates={accessibilityStates}
hasTVPreferredFocus={hasTVPreferredFocus}
testID={testID}
disabled={disabled}
onPress={onPress}
>
<View style={buttonStyles}>
<Text style={textStyles} disabled={disabled}>
{formattedTitle}
</Text>
</View>
</Touchable>
);
}
}
const styles = StyleSheet.create({
button: Platform.select({
ios: {},
android: {
elevation: 4,
// Material design blue from https://material.google.com/style/color.html#color-color-palette
backgroundColor: '#2196F3',
borderRadius: 2,
},
}),
text: {
textAlign: 'center',
padding: 8,
...Platform.select({
ios: {
// iOS blue from https://developer.apple.com/ios/human-interface-guidelines/visual-design/color/
color: '#007AFF',
fontSize: 18,
},
android: {
color: 'white',
fontWeight: '500',
},
}),
},
buttonDisabled: Platform.select({
ios: {},
android: {
elevation: 0,
backgroundColor: '#dfdfdf',
},
}),
textDisabled: Platform.select({
ios: {
color: '#cdcdcd',
},
android: {
color: '#a1a1a1',
},
}),
});

View File

@@ -1,19 +0,0 @@
import { StyleSheet, View, Platform } from 'react-native';
import BaseButton from './Button';
import React from 'react';
export const Button = props => (
<View style={styles.margin}>
<BaseButton {...props} />
</View>
);
const styles = StyleSheet.create({
margin: {
...Platform.select({
android: {
margin: 10,
},
}),
},
});

View File

@@ -1,16 +0,0 @@
import DefaultHeaderButtons from 'react-navigation-header-buttons';
import * as React from 'react';
import { Platform } from 'react-native';
export class HeaderButtons extends React.PureComponent {
static Item = DefaultHeaderButtons.Item;
render() {
return (
<DefaultHeaderButtons
color={Platform.OS === 'ios' ? '#037aff' : 'black'}
{...this.props}
/>
);
}
}

View File

@@ -2,33 +2,29 @@
"name": "NavigationPlayground",
"version": "0.1.0",
"private": true,
"main": "node_modules/expo/AppEntry.js",
"main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"scripts": {
"postinstall": "rm -rf node_modules/react-navigation/{.git,node_modules,examples}",
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"test": "flow"
"start": "react-native-scripts start",
"eject": "react-native-scripts eject",
"android": "react-native-scripts android",
"ios": "react-native-scripts ios",
"test": "node node_modules/jest/bin/jest.js && flow"
},
"dependencies": {
"expo": "^32.0.0",
"hoist-non-react-statics": "^3.0.1",
"invariant": "^2.2.4",
"react": "16.5.0",
"react-native": "https://github.com/expo/react-native/archive/sdk-32.0.0.tar.gz",
"expo": "^25.0.0",
"react": "16.2.0",
"react-native": "^0.52.0",
"react-native-iphone-x-helper": "^1.0.2",
"react-native-paper": "^2.1.3",
"react-navigation": "../..",
"react-navigation-header-buttons": "^0.0.4",
"react-navigation-material-bottom-tabs": "1.0.0"
"react-navigation": "link:../.."
},
"devDependencies": {
"babel-jest": "^22.4.1",
"babel-jest": "^21.0.0",
"babel-plugin-transform-remove-console": "^6.9.0",
"flow-bin": "^0.67.0",
"jest": "^22.1.3",
"jest-expo": "^28.0.0",
"react-test-renderer": "16.3.1"
"flow-bin": "^0.61.0",
"jest": "^21.0.1",
"jest-expo": "^25.1.0",
"react-native-scripts": "^1.5.0",
"react-test-renderer": "16.0.0"
},
"jest": {
"preset": "jest-expo",
@@ -38,6 +34,13 @@
"json",
"jsx",
"node"
],
"modulePathIgnorePatterns": [
"/node_modules/.*/react-native/",
"/node_modules/.*/react/"
],
"transformIgnorePatterns": [
"/node_modules/(?!react-native|react-navigation)/"
]
}
}

View File

@@ -0,0 +1,52 @@
/**
* @noflow
*/
const fs = require('fs');
const path = require('path');
const blacklist = require('metro/src/blacklist');
module.exports = {
getBlacklistRE() {
return blacklist([
/react\-navigation\/examples\/(?!NavigationPlayground).*/,
/react\-navigation\/node_modules\/react-native\/(.*)/,
/react\-navigation\/node_modules\/react\/(.*)/
]);
},
extraNodeModules: getNodeModulesForDirectory(path.resolve('.')),
};
function getNodeModulesForDirectory(rootPath) {
const nodeModulePath = path.join(rootPath, 'node_modules');
const folders = fs.readdirSync(nodeModulePath);
return folders.reduce((modules, folderName) => {
const folderPath = path.join(nodeModulePath, folderName);
if (folderName.startsWith('@')) {
const scopedModuleFolders = fs.readdirSync(folderPath);
const scopedModules = scopedModuleFolders.reduce(
(scopedModules, scopedFolderName) => {
scopedModules[
`${folderName}/${scopedFolderName}`
] = maybeResolveSymlink(path.join(folderPath, scopedFolderName));
return scopedModules;
},
{}
);
return Object.assign({}, modules, scopedModules);
}
modules[folderName] = maybeResolveSymlink(folderPath);
return modules;
}, {});
}
function maybeResolveSymlink(maybeSymlinkPath) {
if (fs.lstatSync(maybeSymlinkPath).isSymbolicLink()) {
const resolved = path.resolve(
path.dirname(maybeSymlinkPath),
fs.readlinkSync(maybeSymlinkPath)
);
return resolved;
}
return maybeSymlinkPath;
}

File diff suppressed because it is too large Load Diff

5
examples/README.md Normal file
View File

@@ -0,0 +1,5 @@
# Examples
## Usage
Please see the [Contributors Guide](https://reactnavigation.org/docs/contributing.html#run-the-example-app) for instructions on running these example apps.

View File

@@ -0,0 +1,8 @@
{
"presets": ["babel-preset-expo"],
"env": {
"development": {
"plugins": ["transform-react-jsx-source"]
}
}
}

3
examples/ReduxExample/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules/
.expo/
npm-debug.*

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,27 @@
import React from 'react';
import { AppRegistry } from 'react-native';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import AppReducer from './src/reducers';
import AppWithNavigationState from './src/navigators/AppNavigator';
import { middleware } from './src/utils/redux';
const store = createStore(
AppReducer,
applyMiddleware(middleware),
);
class ReduxExampleApp extends React.Component {
render() {
return (
<Provider store={store}>
<AppWithNavigationState />
</Provider>
);
}
}
AppRegistry.registerComponent('ReduxExample', () => ReduxExampleApp);
export default ReduxExampleApp;

View File

@@ -0,0 +1,9 @@
import React from 'react';
import App from './App';
import renderer from 'react-test-renderer';
it('renders without crashing', () => {
const rendered = renderer.create(<App />).toJSON();
expect(rendered).toBeTruthy();
});

View File

@@ -0,0 +1,5 @@
# Redux example
## Usage
Please see the [Contributors Guide](https://reactnavigation.org/docs/guides/contributors#Run-the-Example-App) for instructions on running these example apps.

View File

@@ -0,0 +1,24 @@
{
"expo": {
"name": "ReduxExample",
"description": "Try out react-navigation with this awesome Redux example",
"version": "1.0.0",
"slug": "ReduxExample",
"privacy": "public",
"orientation": "portrait",
"primaryColor": "#cccccc",
"icon": "./assets/icons/react-navigation.png",
"loading": {
"icon": "./assets/icons/react-navigation.png",
"hideExponentText": false
},
"sdkVersion": "25.0.0",
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"packagerOpts": {
"assetExts": ["ttf", "mp4"]
},
"ios": {
"supportsTablet": true
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -0,0 +1,40 @@
{
"name": "ReduxExample",
"version": "0.0.1",
"private": true,
"main": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"scripts": {
"start": "react-native-scripts start",
"eject": "react-native-scripts eject",
"android": "react-native-scripts android",
"ios": "react-native-scripts ios",
"test": "node node_modules/jest/bin/jest.js"
},
"jest": {
"preset": "jest-expo",
"modulePathIgnorePatterns": [
"/node_modules/.*/react-native/",
"/node_modules/.*/react/"
],
"transformIgnorePatterns": [
"/node_modules/(?!react-native|react-navigation)/"
]
},
"dependencies": {
"expo": "^25.0.0",
"prop-types": "^15.5.10",
"react": "16.2.0",
"react-native": "^0.52.0",
"react-navigation": "link:../..",
"react-navigation-redux-helpers": "^1.0.0",
"react-redux": "^5.0.6",
"redux": "^3.7.2"
},
"devDependencies": {
"babel-jest": "^21.0.0",
"jest": "^21.0.1",
"jest-expo": "^25.1.0",
"react-native-scripts": "^1.3.1",
"react-test-renderer": "16.0.0"
}
}

View File

@@ -0,0 +1,52 @@
/**
* @noflow
*/
const fs = require('fs');
const path = require('path');
const blacklist = require('metro/src/blacklist');
module.exports = {
getBlacklistRE() {
return blacklist([
/react\-navigation\/examples\/(?!ReduxExample).*/,
/react\-navigation\/node_modules\/react-native\/(.*)/,
/react\-navigation\/node_modules\/react\/(.*)/
]);
},
extraNodeModules: getNodeModulesForDirectory(path.resolve('.')),
};
function getNodeModulesForDirectory(rootPath) {
const nodeModulePath = path.join(rootPath, 'node_modules');
const folders = fs.readdirSync(nodeModulePath);
return folders.reduce((modules, folderName) => {
const folderPath = path.join(nodeModulePath, folderName);
if (folderName.startsWith('@')) {
const scopedModuleFolders = fs.readdirSync(folderPath);
const scopedModules = scopedModuleFolders.reduce(
(scopedModules, scopedFolderName) => {
scopedModules[
`${folderName}/${scopedFolderName}`
] = maybeResolveSymlink(path.join(folderPath, scopedFolderName));
return scopedModules;
},
{}
);
return Object.assign({}, modules, scopedModules);
}
modules[folderName] = maybeResolveSymlink(folderPath);
return modules;
}, {});
}
function maybeResolveSymlink(maybeSymlinkPath) {
if (fs.lstatSync(maybeSymlinkPath).isSymbolicLink()) {
const resolved = path.resolve(
path.dirname(maybeSymlinkPath),
fs.readlinkSync(maybeSymlinkPath)
);
return resolved;
}
return maybeSymlinkPath;
}

View File

@@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Button } from 'react-native';
import { NavigationActions } from 'react-navigation';
const AuthButton = ({ logout, loginScreen, isLoggedIn }) => (
<Button
title={isLoggedIn ? 'Log Out' : 'Open Login Screen'}
onPress={isLoggedIn ? logout : loginScreen}
/>
);
AuthButton.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
logout: PropTypes.func.isRequired,
loginScreen: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
isLoggedIn: state.auth.isLoggedIn,
});
const mapDispatchToProps = dispatch => ({
logout: () => dispatch({ type: 'Logout' }),
loginScreen: () =>
dispatch(NavigationActions.navigate({ routeName: 'Login' })),
});
export default connect(mapStateToProps, mapDispatchToProps)(AuthButton);

View File

@@ -0,0 +1,42 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, StyleSheet, Text, View } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
});
const LoginScreen = ({ navigation }) => (
<View style={styles.container}>
<Text style={styles.welcome}>
Screen A
</Text>
<Text style={styles.instructions}>
This is great
</Text>
<Button
onPress={() => navigation.dispatch({ type: 'Login' })}
title="Log in"
/>
</View>
);
LoginScreen.propTypes = {
navigation: PropTypes.object.isRequired,
};
LoginScreen.navigationOptions = {
title: 'Log In',
};
export default LoginScreen;

View File

@@ -0,0 +1,42 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Button, StyleSheet, Text, View } from 'react-native';
import { NavigationActions } from 'react-navigation';
const styles = StyleSheet.create({
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
});
const LoginStatusMessage = ({ isLoggedIn, dispatch }) => {
if (!isLoggedIn) {
return <Text>Please log in</Text>;
}
return (
<View>
<Text style={styles.welcome}>
{'You are "logged in" right now'}
</Text>
<Button
onPress={() =>
dispatch(NavigationActions.navigate({ routeName: 'Profile' }))}
title="Profile"
/>
</View>
);
};
LoginStatusMessage.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
dispatch: PropTypes.func.isRequired,
};
const mapStateToProps = state => ({
isLoggedIn: state.auth.isLoggedIn,
});
export default connect(mapStateToProps)(LoginStatusMessage);

View File

@@ -0,0 +1,27 @@
import React from 'react';
import { StyleSheet, View } from 'react-native';
import LoginStatusMessage from './LoginStatusMessage';
import AuthButton from './AuthButton';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
});
const MainScreen = () => (
<View style={styles.container}>
<LoginStatusMessage />
<AuthButton />
</View>
);
MainScreen.navigationOptions = {
title: 'Home Screen',
};
export default MainScreen;

View File

@@ -0,0 +1,30 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
});
const ProfileScreen = () => (
<View style={styles.container}>
<Text style={styles.welcome}>
Profile Screen
</Text>
</View>
);
ProfileScreen.navigationOptions = {
title: 'Profile',
};
export default ProfileScreen;

View File

@@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { addNavigationHelpers, StackNavigator } from 'react-navigation';
import LoginScreen from '../components/LoginScreen';
import MainScreen from '../components/MainScreen';
import ProfileScreen from '../components/ProfileScreen';
import { addListener } from '../utils/redux';
export const AppNavigator = StackNavigator({
Login: { screen: LoginScreen },
Main: { screen: MainScreen },
Profile: { screen: ProfileScreen },
});
class AppWithNavigationState extends React.Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
nav: PropTypes.object.isRequired,
};
render() {
const { dispatch, nav } = this.props;
return (
<AppNavigator
navigation={addNavigationHelpers({
dispatch,
state: nav,
addListener,
})}
/>
);
}
}
const mapStateToProps = state => ({
nav: state.nav,
});
export default connect(mapStateToProps)(AppWithNavigationState);

View File

@@ -0,0 +1,57 @@
import { combineReducers } from 'redux';
import { NavigationActions } from 'react-navigation';
import { AppNavigator } from '../navigators/AppNavigator';
// Start with two routes: The Main screen, with the Login screen on top.
const firstAction = AppNavigator.router.getActionForPathAndParams('Main');
const tempNavState = AppNavigator.router.getStateForAction(firstAction);
const secondAction = AppNavigator.router.getActionForPathAndParams('Login');
const initialNavState = AppNavigator.router.getStateForAction(
secondAction,
tempNavState
);
function nav(state = initialNavState, action) {
let nextState;
switch (action.type) {
case 'Login':
nextState = AppNavigator.router.getStateForAction(
NavigationActions.back(),
state
);
break;
case 'Logout':
nextState = AppNavigator.router.getStateForAction(
NavigationActions.navigate({ routeName: 'Login' }),
state
);
break;
default:
nextState = AppNavigator.router.getStateForAction(action, state);
break;
}
// Simply return the original `state` if `nextState` is null or undefined.
return nextState || state;
}
const initialAuthState = { isLoggedIn: false };
function auth(state = initialAuthState, action) {
switch (action.type) {
case 'Login':
return { ...state, isLoggedIn: true };
case 'Logout':
return { ...state, isLoggedIn: false };
default:
return state;
}
}
const AppReducer = combineReducers({
nav,
auth,
});
export default AppReducer;

View File

@@ -0,0 +1,15 @@
import {
createReactNavigationReduxMiddleware,
createReduxBoundAddListener,
} from 'react-navigation-redux-helpers';
const middleware = createReactNavigationReduxMiddleware(
"root",
state => state.nav,
);
const addListener = createReduxBoundAddListener("root");
export {
middleware,
addListener,
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
{
"presets": ["babel-preset-expo"],
"env": {
"development": {
"plugins": ["transform-react-jsx-source"]
}
}
}

3
examples/SafeAreaExample/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules/**/*
.expo/*
npm-debug.*

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,244 @@
import React from 'react';
import { ScrollView, StyleSheet, Text, View } from 'react-native';
import { StackNavigator, withNavigation } from 'react-navigation';
import { Constants } from 'expo';
import Touchable from 'react-native-platform-touchable';
import TabsScreen from './screens/TabsScreen';
import DrawerScreen from './screens/DrawerScreen';
import createDumbStack from './screens/createDumbStack';
import createDumbTabs from './screens/createDumbTabs';
export default class App extends React.Component {
render() {
return <RootStack />;
}
}
@withNavigation
class ExampleItem extends React.Component {
render() {
return (
<View
style={{
borderBottomColor: '#eee',
borderBottomWidth: 1,
}}>
<Touchable
onPress={this._handlePress}
style={{
height: 50,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: this.props.common ? '#fffcd3' : '#fff',
}}>
<Text style={{ fontSize: 15 }}>
{this.props.title} {this.props.common ? '(commonly used)' : null}
</Text>
</Touchable>
</View>
);
}
_handlePress = () => {
this.props.navigation.navigate(this.props.route);
};
}
class ExampleListScreen extends React.Component {
render() {
return (
<View style={{ flex: 1 }}>
<ScrollView
style={{ flex: 1 }}
contentContainerStyle={{ paddingTop: 50, backgroundColor: '#fff' }}>
<Text
style={{
fontSize: 25,
textAlign: 'center',
marginBottom: 20,
paddingBottom: 20,
}}>
SafeAreaView Examples
</Text>
<ExampleItem title="Basic Tabs" route="tabs" common />
{/* <ExampleItem title="Basic Drawer" route="drawer" /> */}
<ExampleItem title="Header height" route="headerHeight" common />
<ExampleItem title="Header padding" route="headerPadding" />
<ExampleItem
title="Header height and padding"
route="headerHeightAndPadding"
/>
<ExampleItem
title="Header padding as percent"
route="headerPaddingPercent"
/>
<ExampleItem title="Header with margin" route="headerMargin" />
<ExampleItem title="Tab bar height" route="tabBarHeight" common />
<ExampleItem title="Tab bar padding" route="tabBarPadding" common />
<ExampleItem
common
title="Tab bar height and padding"
route="tabBarHeightAndPadding"
/>
<ExampleItem title="Tab bar margin" route="tabBarMargin" />
</ScrollView>
<View
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: Constants.statusBarHeight,
backgroundColor: '#fff',
}}
/>
</View>
);
}
}
const StackWithHeaderHeight = createDumbStack({
title: 'height: 150',
headerStyle: { height: 150 },
});
const StackWithHeaderPadding = createDumbStack({
title: 'padding: 100',
headerStyle: { padding: 100 },
});
const StackWithHeaderHeightAndPadding = createDumbStack({
title: 'height: 150, padding: 100',
headerStyle: { height: 150, padding: 100 },
});
const StackWithHeaderPaddingPercent = createDumbStack({
title: 'padding: 60%',
headerStyle: { padding: '60%' },
});
const StackWithHeaderMargin = createDumbStack({
title: 'margin: 20 (but why? :/)',
headerStyle: { margin: 20 },
});
const TabBarWithHeight = createDumbTabs(
{
tabBarLabel: 'label!',
tabBarOptions: {
style: {
height: 100,
},
},
},
createDumbStack({
title: 'tabBar height 100',
})
);
const TabBarWithPadding = createDumbTabs(
{
tabBarLabel: 'label!',
tabBarOptions: {
style: {
padding: 20,
},
},
},
createDumbStack({
title: 'tabBar padding 20',
})
);
const TabBarWithHeightAndPadding = createDumbTabs(
{
tabBarLabel: 'label!',
tabBarOptions: {
style: {
padding: 20,
height: 100,
},
},
},
createDumbStack({
title: 'tabBar height 100 padding 20',
})
);
const TabBarWithMargin = createDumbTabs(
{
tabBarLabel: 'label!',
tabBarOptions: {
style: {
margin: 20,
},
},
},
createDumbStack({
title: 'tabBar margin 20',
})
);
const RootStack = StackNavigator(
{
exampleList: {
screen: ExampleListScreen,
},
tabs: {
screen: TabsScreen,
},
headerHeight: {
screen: StackWithHeaderHeight,
},
headerPadding: {
screen: StackWithHeaderPadding,
},
headerHeightAndPadding: {
screen: StackWithHeaderHeightAndPadding,
},
headerPaddingPercent: {
screen: StackWithHeaderPaddingPercent,
},
headerMargin: {
screen: StackWithHeaderMargin,
},
tabBarHeight: {
screen: TabBarWithHeight,
},
tabBarPadding: {
screen: TabBarWithPadding,
},
tabBarHeightAndPadding: {
screen: TabBarWithHeightAndPadding,
},
tabBarMargin: {
screen: TabBarWithMargin,
},
},
{
headerMode: 'none',
cardStyle: {
backgroundColor: '#fff',
},
}
);
// basic tabs (different navbar color, different tabbar color)
// different header height
// different header padding
// different header height and padding
// different header margin
// different tabbar height
// different tabbar padding
// different tabbar height and padding
// different tabbar margin
// without navbar, without safeareaview in one tab and with safeareaview in another tab
// all should be able to toggle between landscape and portrait
// basic drawer (different navbar color, mess around with drawer options)
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});

View File

@@ -0,0 +1,23 @@
{
"expo": {
"name": "SafeAreaExample",
"description": "An empty new project",
"slug": "SafeAreaExample",
"privacy": "public",
"sdkVersion": "25.0.0",
"version": "1.0.0",
"primaryColor": "#cccccc",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"packagerOpts": {
"assetExts": ["ttf", "mp4"]
},
"ios": {
"supportsTablet": true
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -0,0 +1,15 @@
{
"main": "node_modules/expo/AppEntry.js",
"private": true,
"dependencies": {
"expo": "^25.0.0",
"react": "16.2.0",
"react-native": "^0.52.0",
"react-native-platform-touchable": "^1.1.1",
"react-navigation": "link:../.."
},
"name": "SafeAreaExample",
"version": "0.0.0",
"description": "Hello Expo!",
"author": null
}

View File

@@ -0,0 +1,52 @@
/**
* @noflow
*/
const fs = require('fs');
const path = require('path');
const blacklist = require('metro/src/blacklist');
module.exports = {
getBlacklistRE() {
return blacklist([
/react\-navigation\/examples\/(?!SafeAreaExample).*/,
/react\-navigation\/node_modules\/react-native\/(.*)/,
/react\-navigation\/node_modules\/react\/(.*)/
]);
},
extraNodeModules: getNodeModulesForDirectory(path.resolve('.')),
};
function getNodeModulesForDirectory(rootPath) {
const nodeModulePath = path.join(rootPath, 'node_modules');
const folders = fs.readdirSync(nodeModulePath);
return folders.reduce((modules, folderName) => {
const folderPath = path.join(nodeModulePath, folderName);
if (folderName.startsWith('@')) {
const scopedModuleFolders = fs.readdirSync(folderPath);
const scopedModules = scopedModuleFolders.reduce(
(scopedModules, scopedFolderName) => {
scopedModules[
`${folderName}/${scopedFolderName}`
] = maybeResolveSymlink(path.join(folderPath, scopedFolderName));
return scopedModules;
},
{}
);
return Object.assign({}, modules, scopedModules);
}
modules[folderName] = maybeResolveSymlink(folderPath);
return modules;
}, {});
}
function maybeResolveSymlink(maybeSymlinkPath) {
if (fs.lstatSync(maybeSymlinkPath).isSymbolicLink()) {
const resolved = path.resolve(
path.dirname(maybeSymlinkPath),
fs.readlinkSync(maybeSymlinkPath)
);
return resolved;
}
return maybeSymlinkPath;
}

View File

@@ -0,0 +1,3 @@
import createDumbStack from './createDumbStack';
export default createDumbStack();

View File

@@ -0,0 +1,3 @@
import createDumbTabs from './createDumbTabs';
export default createDumbTabs();

View File

@@ -0,0 +1,99 @@
import React from 'react';
import { StackNavigator } from 'react-navigation';
import {
Dimensions,
Button,
Platform,
ScrollView,
Text,
View,
StatusBar,
} from 'react-native';
import { ScreenOrientation } from 'expo';
const Separator = () => (
<View
style={{
width: Dimensions.get('window').width - 100,
height: 1,
backgroundColor: '#ccc',
marginHorizontal: 50,
marginTop: 15,
marginBottom: 15,
}}
/>
);
const Spacer = () => (
<View
style={{
marginBottom: Platform.OS === 'android' ? 20 : 5,
}}
/>
);
export default (navigationOptions = {}) => {
class DumbScreen extends React.Component {
static navigationOptions = {
title: 'Title!',
...navigationOptions,
headerStyle: {
backgroundColor: '#6b52ae',
...navigationOptions.headerStyle,
},
headerTitleStyle: {
color: '#fff',
...navigationOptions.headerTitleStyle,
},
};
render() {
return (
<ScrollView style={{ flex: 1 }}>
<View
style={{
paddingTop: 30,
alignItems: 'center',
justifyContent: 'center',
}}>
<Button onPress={this._goBack} title="Go back" />
<Separator />
<Button onPress={this._setPortrait} title="Set portrait" />
<Spacer />
<Button onPress={this._setLandscape} title="Set landscape" />
<Separator />
<Button onPress={this._hideStatusBar} title="Hide status bar" />
<Spacer />
<Button onPress={this._showStatusBar} title="Show status bar" />
</View>
</ScrollView>
);
}
_goBack = () => {
this.props.navigation.goBack(null);
};
_setPortrait = () => {
ScreenOrientation.allow(ScreenOrientation.Orientation.PORTRAIT);
};
_setLandscape = () => {
ScreenOrientation.allow(ScreenOrientation.Orientation.LANDSCAPE);
};
_hideStatusBar = () => {
StatusBar.setHidden(true, 'slide');
};
_showStatusBar = () => {
StatusBar.setHidden(false, 'slide');
};
}
return StackNavigator({
dumb: {
screen: DumbScreen,
},
});
};

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { Platform, View } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { TabNavigator, TabBarBottom } from 'react-navigation';
import createDumbStack from './createDumbStack';
export default (navigationOptions = {}, DumbStack = createDumbStack()) => {
return TabNavigator(
{
Home: {
screen: DumbStack,
},
Links: {
screen: DumbStack,
},
Settings: {
screen: DumbStack,
},
},
{
navigationOptions: ({ navigation }) => ({
...navigationOptions,
tabBarIcon: ({ focused }) => {
const { routeName } = navigation.state;
let iconName;
switch (routeName) {
case 'Home':
iconName =
Platform.OS === 'ios'
? `ios-information-circle${focused ? '' : '-outline'}`
: 'md-information-circle';
break;
case 'Links':
iconName =
Platform.OS === 'ios'
? `ios-link${focused ? '' : '-outline'}`
: 'md-link';
break;
case 'Settings':
iconName =
Platform.OS === 'ios'
? `ios-options${focused ? '' : '-outline'}`
: 'md-options';
}
return (
<Ionicons
name={iconName}
size={28}
style={{ marginBottom: -3 }}
color={focused ? '#6b52ae' : '#ccc'}
/>
);
},
}),
tabBarOptions: {
activeTintColor: '#6b52ae',
...navigationOptions.tabBarOptions,
style: {
backgroundColor: '#F5F1FF',
...(navigationOptions.tabBarOptions ? navigationOptions.tabBarOptions.style : {}),
}
},
tabBarComponent: TabBarBottom,
tabBarPosition: 'bottom',
animationEnabled: false,
swipeEnabled: false,
}
);
};

File diff suppressed because it is too large Load Diff

View File

@@ -69,14 +69,6 @@ declare module 'react-navigation' {
[key: string]: mixed,
};
declare export type NavigationBackAction = {|
type: 'Navigation/BACK',
key?: ?string,
|};
declare export type NavigationInitAction = {|
type: 'Navigation/INIT',
params?: NavigationParams,
|};
declare export type NavigationNavigateAction = {|
type: 'Navigation/NAVIGATE',
routeName: string,
@@ -87,6 +79,12 @@ declare module 'react-navigation' {
key?: string,
|};
declare export type NavigationBackAction = {|
type: 'Navigation/BACK',
key?: ?string,
|};
declare export type NavigationSetParamsAction = {|
type: 'Navigation/SET_PARAMS',
@@ -97,6 +95,30 @@ declare module 'react-navigation' {
params: NavigationParams,
|};
declare export type NavigationInitAction = {|
type: 'Navigation/INIT',
params?: NavigationParams,
|};
declare export type NavigationResetAction = {|
type: 'Navigation/RESET',
index: number,
key?: ?string,
actions: Array<NavigationNavigateAction>,
|};
declare export type NavigationUriAction = {|
type: 'Navigation/URI',
uri: string,
|};
declare export type NavigationReplaceAction = {|
+type: 'Navigation/REPLACE',
+key: string,
+routeName: string,
+params?: NavigationParams,
+action?: NavigationNavigateAction,
|};
declare export type NavigationPopAction = {|
+type: 'Navigation/POP',
+n?: number,
@@ -113,61 +135,17 @@ declare module 'react-navigation' {
+action?: NavigationNavigateAction,
+key?: string,
|};
declare export type NavigationResetAction = {|
type: 'Navigation/RESET',
index: number,
key?: ?string,
actions: Array<NavigationNavigateAction>,
|};
declare export type NavigationReplaceAction = {|
+type: 'Navigation/REPLACE',
+key: string,
+routeName: string,
+params?: NavigationParams,
+action?: NavigationNavigateAction,
|};
declare export type NavigationCompleteTransitionAction = {|
+type: 'Navigation/COMPLETE_TRANSITION',
+key?: string,
|};
declare export type NavigationOpenDrawerAction = {|
+type: 'Navigation/OPEN_DRAWER',
+key?: string,
|};
declare export type NavigationCloseDrawerAction = {|
+type: 'Navigation/CLOSE_DRAWER',
+key?: string,
|};
declare export type NavigationToggleDrawerAction = {|
+type: 'Navigation/TOGGLE_DRAWER',
+key?: string,
|};
declare export type NavigationDrawerOpenedAction = {|
+type: 'Navigation/DRAWER_OPENED',
+key?: string,
|};
declare export type NavigationDrawerClosedAction = {|
+type: 'Navigation/DRAWER_CLOSED',
+key?: string,
|};
declare export type NavigationAction =
| NavigationBackAction
| NavigationInitAction
| NavigationNavigateAction
| NavigationSetParamsAction
| NavigationReplaceAction
| NavigationPopAction
| NavigationPopToTopAction
| NavigationPushAction
| NavigationResetAction
| NavigationReplaceAction
| NavigationCompleteTransitionAction
| NavigationOpenDrawerAction
| NavigationCloseDrawerAction
| NavigationToggleDrawerAction
| NavigationDrawerOpenedAction
| NavigationDrawerClosedAction;
| NavigationBackAction
| NavigationSetParamsAction
| NavigationResetAction;
/**
* NavigationState is a tree of routes for a single navigator, where each
@@ -194,7 +172,7 @@ declare module 'react-navigation' {
| NavigationLeafRoute
| NavigationStateRoute;
declare export type NavigationLeafRoute = {|
declare export type NavigationLeafRoute = {
/**
* React's key used by some navigators. No need to specify these manually,
* they will be defined by the router.
@@ -214,12 +192,10 @@ declare module 'react-navigation' {
* e.g. `{ car_id: 123 }` in a route that displays a car.
*/
params?: NavigationParams,
|};
};
declare export type NavigationStateRoute = {|
...NavigationLeafRoute,
...$Exact<NavigationState>,
|};
declare export type NavigationStateRoute = NavigationLeafRoute &
NavigationState;
/**
* Router
@@ -293,36 +269,24 @@ declare module 'react-navigation' {
declare export type NavigationComponent =
| NavigationScreenComponent<NavigationRoute, *, *>
| NavigationNavigator<*, *, *>;
declare interface withOptionalNavigationOptions<Options> {
navigationOptions?: NavigationScreenConfig<Options>;
}
| NavigationContainer<*, *, *>
| any;
declare export type NavigationScreenComponent<
Route: NavigationRoute,
Options: {},
Props: {}
> = React$ComponentType<{
...Props,
...NavigationNavigatorProps<Options, Route>,
}> &
withOptionalNavigationOptions<Options>;
declare interface withRouter<State, Options> {
router: NavigationRouter<State, Options>;
}
> = React$ComponentType<NavigationNavigatorProps<Options, Route> & Props> &
({} | { navigationOptions: NavigationScreenConfig<Options> });
declare export type NavigationNavigator<
State: NavigationState,
Options: {},
Props: {}
> = React$StatelessFunctionalComponent<{
...Props,
...NavigationNavigatorProps<Options, State>,
}> &
withRouter<State, Options> &
withOptionalNavigationOptions<Options>;
> = React$ComponentType<NavigationNavigatorProps<Options, State> & Props> & {
router: NavigationRouter<State, Options>,
navigationOptions?: ?NavigationScreenConfig<Options>,
};
declare export type NavigationRouteConfig =
| NavigationComponent
@@ -332,6 +296,7 @@ declare module 'react-navigation' {
} & NavigationScreenRouteConfig);
declare export type NavigationScreenRouteConfig =
| NavigationComponent
| {
screen: NavigationComponent,
}
@@ -379,7 +344,7 @@ declare module 'react-navigation' {
headerTintColor?: string,
headerLeft?: React$Node | React$ElementType,
headerBackTitle?: string,
headerBackImage?: React$Node | React$ElementType,
headerBackImage?: ImageSource,
headerTruncatedBackTitle?: string,
headerBackTitleStyle?: TextStyleProp,
headerPressColorAndroid?: string,
@@ -396,7 +361,7 @@ declare module 'react-navigation' {
initialRouteName?: string,
initialRouteParams?: NavigationParams,
paths?: NavigationPathsConfig,
defaultNavigationOptions?: NavigationScreenConfig<*>,
navigationOptions?: NavigationScreenConfig<*>,
initialRouteKey?: string,
|};
@@ -404,18 +369,10 @@ declare module 'react-navigation' {
mode?: 'card' | 'modal',
headerMode?: HeaderMode,
headerTransitionPreset?: 'fade-in-place' | 'uikit',
headerLayoutPreset?: 'left' | 'center',
headerBackTitleVisible?: boolean,
cardStyle?: ViewStyleProp,
transitionConfig?: (
transitionProps: NavigationTransitionProps,
prevTransitionProps: ?NavigationTransitionProps,
isModal: boolean
) => TransitionConfig,
transitionConfig?: () => TransitionConfig,
onTransitionStart?: () => void,
onTransitionEnd?: () => void,
transparentCard?: boolean,
disableKeyboardHandling?: boolean,
|};
declare export type StackNavigatorConfig = {|
@@ -423,20 +380,6 @@ declare module 'react-navigation' {
...NavigationStackRouterConfig,
|};
/**
* Switch Navigator
*/
declare export type NavigationSwitchRouterConfig = {|
initialRouteName?: string,
initialRouteParams?: NavigationParams,
paths?: NavigationPathsConfig,
defaultNavigationOptions?: NavigationScreenConfig<*>,
order?: Array<string>,
backBehavior?: 'none' | 'initialRoute', // defaults to `'none'`
resetOnBlur?: boolean, // defaults to `true`
|};
/**
* Tab Navigator
*/
@@ -445,9 +388,10 @@ declare module 'react-navigation' {
initialRouteName?: string,
initialRouteParams?: NavigationParams,
paths?: NavigationPathsConfig,
defaultNavigationOptions?: NavigationScreenConfig<*>,
navigationOptions?: NavigationScreenConfig<*>,
// todo: type these as the real route names rather than 'string'
order?: Array<string>,
// Does the back button cause the router to switch to the initial tab
backBehavior?: 'none' | 'initialRoute', // defaults `initialRoute`
|};
@@ -470,10 +414,10 @@ declare module 'react-navigation' {
| ((options: { tintColor: ?string, focused: boolean }) => ?React$Node),
tabBarVisible?: boolean,
tabBarTestIDProps?: { testID?: string, accessibilityLabel?: string },
tabBarOnPress?: ({
navigation: NavigationScreenProp<NavigationRoute>,
defaultHandler: () => void,
}) => void,
tabBarOnPress?: (
scene: TabScene,
jumpToIndex: (index: number) => void
) => void,
|};
/**
@@ -529,68 +473,29 @@ declare module 'react-navigation' {
declare export type NavigationScreenProp<+S> = {
+state: S,
dispatch: NavigationDispatch,
addListener: (
eventName: string,
callback: NavigationEventCallback
) => NavigationEventSubscription,
getParam: <ParamName: string>(
paramName: ParamName,
fallback?: $ElementType<
$PropertyType<
{|
...{| params: {| [ParamName]: void |} |},
...$Exact<S>,
|},
'params'
>,
ParamName
>
) => $ElementType<
$PropertyType<
{|
...{| params: {| [ParamName]: void |} |},
...$Exact<S>,
|},
'params'
>,
ParamName
>,
dangerouslyGetParent: () => NavigationScreenProp<*>,
isFocused: () => boolean,
// Shared action creators that exist for all routers
goBack: (routeKey?: ?string) => boolean,
navigate: (
routeName:
| string
| {
routeName: string,
params?: NavigationParams,
action?: NavigationNavigateAction,
key?: string,
},
routeName: string,
params?: NavigationParams,
action?: NavigationNavigateAction
) => boolean,
setParams: (newParams: NavigationParams) => boolean,
// StackRouter action creators
pop?: (n?: number, params?: { immediate?: boolean }) => boolean,
popToTop?: (params?: { immediate?: boolean }) => boolean,
push?: (
addListener: (
eventName: string,
callback: NavigationEventCallback
) => NavigationEventSubscription,
push: (
routeName: string,
params?: NavigationParams,
action?: NavigationNavigateAction
) => boolean,
replace?: (
replace: (
routeName: string,
params?: NavigationParams,
action?: NavigationNavigateAction
) => boolean,
reset?: (actions: NavigationAction[], index: number) => boolean,
dismiss?: () => boolean,
// DrawerRouter action creators
openDrawer?: () => boolean,
closeDrawer?: () => boolean,
toggleDrawer?: () => boolean,
pop: (n?: number, params?: { immediate?: boolean }) => boolean,
popToTop: (params?: { immediate?: boolean }) => boolean,
};
declare export type NavigationNavigatorProps<O: {}, S: {}> = $Shape<{
@@ -599,21 +504,6 @@ declare module 'react-navigation' {
navigationOptions?: O,
}>;
/**
* NavigationEvents component
*/
declare type _NavigationEventsProps = {
navigation?: NavigationScreenProp<NavigationState>,
onWillFocus?: NavigationEventCallback,
onDidFocus?: NavigationEventCallback,
onWillBlur?: NavigationEventCallback,
onDidBlur?: NavigationEventCallback,
};
declare export var NavigationEvents: React$ComponentType<
_NavigationEventsProps
>;
/**
* Navigation container
*/
@@ -622,23 +512,19 @@ declare module 'react-navigation' {
State: NavigationState,
Options: {},
Props: {}
> = React$ComponentType<{
...Props,
...NavigationContainerProps<State, Options>,
}> &
withRouter<State, Options> &
withOptionalNavigationOptions<Options>;
> = React$ComponentType<NavigationContainerProps<State, Options> & Props> & {
router: NavigationRouter<State, Options>,
navigationOptions?: ?NavigationScreenConfig<Options>,
};
declare export type NavigationContainerProps<S: {}, O: {}> = $Shape<{
uriPrefix?: string | RegExp,
onNavigationStateChange?: ?(
onNavigationStateChange?: (
NavigationState,
NavigationState,
NavigationAction
) => void,
navigation?: NavigationScreenProp<S>,
persistenceKey?: ?string,
renderLoadingExperimental?: React$ComponentType<{}>,
screenProps?: *,
navigationOptions?: O,
}>;
@@ -678,8 +564,8 @@ declare module 'react-navigation' {
// The value that represents the progress of the transition when navigation
// state changes from one to another. Its numeric value will range from 0
// to 1.
// progress.__getAnimatedValue() < 1 : transition is happening.
// progress.__getAnimatedValue() == 1 : transition completes.
// progress.__getAnimatedValue() < 1 : transtion is happening.
// progress.__getAnimatedValue() == 1 : transtion completes.
progress: AnimatedValue,
// All the scenes of the transitioner.
@@ -761,9 +647,6 @@ declare module 'react-navigation' {
* Now we type the actual exported module
*/
declare export function createAppContainer<S: NavigationState, O: {}>(
Component: NavigationNavigator<S, O, *>
): NavigationContainer<S, O, *>;
declare export function createNavigationContainer<S: NavigationState, O: {}>(
Component: NavigationNavigator<S, O, *>
): NavigationContainer<S, O, *>;
@@ -799,75 +682,44 @@ declare module 'react-navigation' {
BACK: 'Navigation/BACK',
INIT: 'Navigation/INIT',
NAVIGATE: 'Navigation/NAVIGATE',
SET_PARAMS: 'Navigation/SET_PARAMS',
back: (payload?: { key?: ?string }) => NavigationBackAction,
init: (payload?: { params?: NavigationParams }) => NavigationInitAction,
navigate: (payload: {
routeName: string,
params?: ?NavigationParams,
action?: ?NavigationNavigateAction,
key?: string,
}) => NavigationNavigateAction,
setParams: (payload: {
key: string,
params: NavigationParams,
}) => NavigationSetParamsAction,
};
declare export var StackActions: {
POP: 'Navigation/POP',
POP_TO_TOP: 'Navigation/POP_TO_TOP',
PUSH: 'Navigation/PUSH',
RESET: 'Navigation/RESET',
REPLACE: 'Navigation/REPLACE',
COMPLETE_TRANSITION: 'Navigation/COMPLETE_TRANSITION',
pop: (payload: {
n?: number,
immediate?: boolean,
}) => NavigationPopAction,
popToTop: (payload: {
immediate?: boolean,
}) => NavigationPopToTopAction,
push: (payload: {
routeName: string,
params?: NavigationParams,
action?: NavigationNavigateAction,
key?: string,
}) => NavigationPushAction,
reset: (payload: {
index: number,
key?: ?string,
actions: Array<NavigationNavigateAction>,
}) => NavigationResetAction,
replace: (payload: {
key?: string,
routeName: string,
params?: NavigationParams,
action?: NavigationNavigateAction,
}) => NavigationReplaceAction,
completeTransition: (payload: {
key?: string,
}) => NavigationCompleteTransitionAction,
};
declare export var DrawerActions: {
OPEN_DRAWER: 'Navigation/OPEN_DRAWER',
CLOSE_DRAWER: 'Navigation/CLOSE_DRAWER',
TOGGLE_DRAWER: 'Navigation/TOGGLE_DRAWER',
DRAWER_OPENED: 'Navigation/DRAWER_OPENED',
DRAWER_CLOSED: 'Navigation/DRAWER_CLOSED',
openDrawer: (payload: {
key?: string,
}) => NavigationOpenDrawerAction,
closeDrawer: (payload: {
key?: string,
}) => NavigationCloseDrawerAction,
toggleDrawer: (payload: {
key?: string,
}) => NavigationToggleDrawerAction,
SET_PARAMS: 'Navigation/SET_PARAMS',
URI: 'Navigation/URI',
back: {
(payload?: { key?: ?string }): NavigationBackAction,
toString: () => string,
},
init: {
(payload?: { params?: NavigationParams }): NavigationInitAction,
toString: () => string,
},
navigate: {
(payload: {
routeName: string,
params?: ?NavigationParams,
action?: ?NavigationNavigateAction,
}): NavigationNavigateAction,
toString: () => string,
},
reset: {
(payload: {
index: number,
key?: ?string,
actions: Array<NavigationNavigateAction>,
}): NavigationResetAction,
toString: () => string,
},
setParams: {
(payload: {
key: string,
params: NavigationParams,
}): NavigationSetParamsAction,
toString: () => string,
},
uri: {
(payload: { uri: string }): NavigationUriAction,
toString: () => string,
},
};
declare type _RouterProp<S: NavigationState, O: {}> = {
@@ -890,16 +742,12 @@ declare module 'react-navigation' {
view: NavigationView<O, S>,
router: NavigationRouter<S, O>,
navigatorConfig?: NavigatorConfig
): NavigationNavigator<S, O, *>;
): any;
declare export function StackNavigator(
routeConfigMap: NavigationRouteConfigMap,
stackConfig?: StackNavigatorConfig
): NavigationNavigator<*, *, *>;
declare export function createStackNavigator(
routeConfigMap: NavigationRouteConfigMap,
stackConfig?: StackNavigatorConfig
): NavigationNavigator<*, *, *>;
): NavigationContainer<*, *, *>;
declare type _TabViewConfig = {|
tabBarComponent?: React$ElementType,
@@ -923,31 +771,7 @@ declare module 'react-navigation' {
declare export function TabNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _TabNavigatorConfig
): NavigationNavigator<*, *, *>;
declare export function createTabNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _TabNavigatorConfig
): NavigationNavigator<*, *, *>;
/* TODO: fix the config for each of these tab navigator types */
declare export function createBottomTabNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _TabNavigatorConfig
): NavigationNavigator<*, *, *>;
declare export function createMaterialTopTabNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _TabNavigatorConfig
): NavigationNavigator<*, *, *>;
declare type _SwitchNavigatorConfig = {|
...NavigationSwitchRouterConfig,
|};
declare export function SwitchNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _SwitchNavigatorConfig
): NavigationNavigator<*, *, *>;
declare export function createSwitchNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _SwitchNavigatorConfig
): NavigationNavigator<*, *, *>;
): NavigationContainer<*, *, *>;
declare type _DrawerViewConfig = {|
drawerLockMode?: 'unlocked' | 'locked-closed' | 'locked-open',
@@ -968,11 +792,7 @@ declare module 'react-navigation' {
declare export function DrawerNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _DrawerNavigatorConfig
): NavigationNavigator<*, *, *>;
declare export function createDrawerNavigator(
routeConfigs: NavigationRouteConfigMap,
config?: _DrawerNavigatorConfig
): NavigationNavigator<*, *, *>;
): NavigationContainer<*, *, *>;
declare export function StackRouter(
routeConfigs: NavigationRouteConfigMap,
@@ -1058,14 +878,12 @@ declare module 'react-navigation' {
vertical?: _SafeAreaViewForceInsetValue,
horizontal?: _SafeAreaViewForceInsetValue,
},
children?: React$Node,
children: React$Node,
style?: AnimatedViewStyleProp,
};
declare export var SafeAreaView: React$ComponentType<_SafeAreaViewProps>;
declare export var Header: React$ComponentType<HeaderProps> & {
HEIGHT: number,
};
declare export var Header: React$ComponentType<HeaderProps>;
declare type _HeaderTitleProps = {
children: React$Node,
@@ -1208,26 +1026,13 @@ declare module 'react-navigation' {
};
declare export var TabBarBottom: React$ComponentType<_TabBarBottomProps>;
declare export function withNavigation<Props: {}, ComponentType: React$ComponentType<Props>>(
Component: ComponentType
): React$ComponentType<
$Diff<
React$ElementConfig<ComponentType>,
{
navigation: NavigationScreenProp<NavigationStateRoute> | void,
}
>
>;
declare export function withNavigationFocus<Props: {}, ComponentType: React$ComponentType<Props>>(
Component: ComponentType
): React$ComponentType<$Diff<React$ElementConfig<ComponentType>, { isFocused: boolean | void }>>;
declare export function getNavigation<State: NavigationState, Options: {}>(
router: NavigationRouter<State, Options>,
state: State,
dispatch: NavigationDispatch,
actionSubscribers: Set<NavigationEventCallback>,
getScreenProps: () => {},
getCurrentNavigation: () => ?NavigationScreenProp<State>
): NavigationScreenProp<State>;
declare type _NavigationInjectedProps = {
navigation: NavigationScreenProp<NavigationStateRoute>,
};
declare export function withNavigation<T: {}>(
Component: React$ComponentType<T & _NavigationInjectedProps>
): React$ComponentType<T>;
declare export function withNavigationFocus<T: {}>(
Component: React$ComponentType<T & _NavigationInjectedProps>
): React$ComponentType<T>;
}

View File

@@ -1,13 +1,14 @@
{
"name": "react-navigation",
"version": "3.1.5",
"version": "1.2.1",
"description": "Routing and navigation for your React Native apps",
"main": "src/react-navigation.js",
"repository": {
"url": "git@github.com:react-navigation/react-navigation.git",
"type": "git"
},
"author": "Adam Miskiewicz <adam@sk3vy.com>, Eric Vicenti <ericvicenti@gmail.com>, Brent Vatne <brent@expo.io>",
"author":
"Adam Miskiewicz <adam@sk3vy.com>, Eric Vicenti <ericvicenti@gmail.com>",
"license": "BSD-2-Clause",
"scripts": {
"start": "npm run ios",
@@ -19,80 +20,56 @@
"test-update-snapshot": "jest --updateSnapshot",
"lint": "eslint .",
"format": "eslint --fix .",
"precommit": "lint-staged",
"release": "release-it"
"precommit": "lint-staged"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/"
},
"files": [
"src",
"NavigationTestUtils.js"
],
"files": ["src"],
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"dependencies": {
"@react-navigation/core": "3.0.3",
"@react-navigation/native": "3.1.4",
"react-navigation-drawer": "1.1.0",
"react-navigation-stack": "1.0.9",
"react-navigation-tabs": "1.0.2"
"clamp": "^1.0.1",
"hoist-non-react-statics": "^2.2.0",
"path-to-regexp": "^1.7.0",
"prop-types": "^15.5.10",
"react-native-drawer-layout-polyfill": "^1.3.2",
"react-native-safe-area-view": "^0.7.0",
"react-native-tab-view": "^0.0.74"
},
"devDependencies": {
"babel-cli": "^6.24.1",
"babel-core": "^6.25.0",
"babel-eslint": "^7.2.3",
"babel-jest": "^22.4.1",
"babel-jest": "^20.0.3",
"babel-preset-react-native": "^2.1.0",
"codecov": "^2.2.0",
"conventional-changelog-cli": "^2.0.5",
"eslint": "^4.2.0",
"eslint-config-prettier": "^2.9.0",
"eslint-config-prettier": "^2.3.0",
"eslint-plugin-import": "^2.7.0",
"eslint-plugin-jsx-a11y": "^6.0.2",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-prettier": "^2.1.2",
"eslint-plugin-react": "^7.1.0",
"husky": "^0.14.3",
"jest": "^22.1.3",
"jest-expo": "^25.1.0",
"lint-staged": "^4.2.1",
"prettier": "^1.12.1",
"prettier-eslint": "^8.8.1",
"prettier": "^1.5.3",
"prettier-eslint": "^6.4.2",
"react": "16.2.0",
"react-native": "^0.52.0",
"react-native-vector-icons": "^4.2.0",
"react-test-renderer": "^16.0.0",
"release-it": "^7.6.1"
"react-test-renderer": "^16.0.0"
},
"jest": {
"notify": true,
"preset": "react-native",
"testRegex": "/__tests__/[^/]+-test\\.js$",
"setupFiles": [
"<rootDir>/jest-setup.js"
],
"testRegex": "./src/.*\\-test\\.js$",
"setupFiles": ["<rootDir>/jest-setup.js"],
"coverageDirectory": "./coverage/",
"collectCoverage": true,
"coverageReporters": [
"lcov"
],
"collectCoverageFrom": [
"src/**/*.js"
],
"coveragePathIgnorePatterns": [
"jest-setup.js"
],
"moduleNameMapper": {
"\\.png$": "<rootDir>/assetsTransformer.js"
},
"modulePathIgnorePatterns": [
"<rootDir>/examples/"
],
"transformIgnorePatterns": [
"node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation-deprecated-tab-navigator|react-navigation-stack|@react-navigation/core|@react-navigation/native)"
]
"coverageReporters": ["lcov"],
"collectCoverageFrom": ["src/**/*.js"],
"coveragePathIgnorePatterns": ["jest-setup.js"],
"modulePathIgnorePatterns": ["examples"]
},
"lint-staged": {
"*.js": [

View File

@@ -4,6 +4,6 @@ set -eo pipefail
case $CIRCLE_NODE_INDEX in
0) yarn test && yarn codecov ;;
#1) cd examples/NavigationPlayground && yarn && yarn test ;;
1) yarn link && cd examples/NavigationPlayground && yarn && yarn link react-navigation && yarn test ;;
#2) cd examples/ReduxExample && yarn && yarn test ;;
esac

155
src/NavigationActions.js Normal file
View File

@@ -0,0 +1,155 @@
const BACK = 'Navigation/BACK';
const INIT = 'Navigation/INIT';
const NAVIGATE = 'Navigation/NAVIGATE';
const POP = 'Navigation/POP';
const POP_TO_TOP = 'Navigation/POP_TO_TOP';
const PUSH = 'Navigation/PUSH';
const RESET = 'Navigation/RESET';
const REPLACE = 'Navigation/REPLACE';
const SET_PARAMS = 'Navigation/SET_PARAMS';
const URI = 'Navigation/URI';
const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
const OPEN_DRAWER = 'Navigation/OPEN_DRAWER';
const CLOSE_DRAWER = 'Navigation/CLOSE_DRAWER';
const TOGGLE_DRAWER = 'Navigation/TOGGLE_DRAWER';
const createAction = (type, fn) => {
fn.toString = () => type;
return fn;
};
const back = createAction(BACK, (payload = {}) => ({
type: BACK,
key: payload.key,
immediate: payload.immediate,
}));
const init = createAction(INIT, (payload = {}) => {
const action = {
type: INIT,
};
if (payload.params) {
action.params = payload.params;
}
return action;
});
const navigate = createAction(NAVIGATE, payload => {
const action = {
type: NAVIGATE,
routeName: payload.routeName,
};
if (payload.params) {
action.params = payload.params;
}
if (payload.action) {
action.action = payload.action;
}
if (payload.key) {
action.key = payload.key;
}
return action;
});
const pop = createAction(POP, payload => ({
type: POP,
n: payload && payload.n,
immediate: payload && payload.immediate,
}));
const popToTop = createAction(POP_TO_TOP, payload => ({
type: POP_TO_TOP,
immediate: payload && payload.immediate,
key: payload && payload.key,
}));
const push = createAction(PUSH, payload => {
const action = {
type: PUSH,
routeName: payload.routeName,
};
if (payload.params) {
action.params = payload.params;
}
if (payload.action) {
action.action = payload.action;
}
return action;
});
const reset = createAction(RESET, payload => ({
type: RESET,
index: payload.index,
key: payload.key,
actions: payload.actions,
}));
const replace = createAction(REPLACE, payload => ({
type: REPLACE,
key: payload.key,
newKey: payload.newKey,
params: payload.params,
action: payload.action,
routeName: payload.routeName,
immediate: payload.immediate,
}));
const setParams = createAction(SET_PARAMS, payload => ({
type: SET_PARAMS,
key: payload.key,
params: payload.params,
}));
const uri = createAction(URI, payload => ({
type: URI,
uri: payload.uri,
}));
const completeTransition = createAction(COMPLETE_TRANSITION, payload => ({
type: COMPLETE_TRANSITION,
key: payload && payload.key,
}));
const openDrawer = createAction(OPEN_DRAWER, payload => ({
type: OPEN_DRAWER,
}));
const closeDrawer = createAction(CLOSE_DRAWER, payload => ({
type: CLOSE_DRAWER,
}));
const toggleDrawer = createAction(TOGGLE_DRAWER, payload => ({
type: TOGGLE_DRAWER,
}));
export default {
// Action constants
BACK,
INIT,
NAVIGATE,
POP,
POP_TO_TOP,
PUSH,
RESET,
REPLACE,
SET_PARAMS,
URI,
COMPLETE_TRANSITION,
OPEN_DRAWER,
CLOSE_DRAWER,
TOGGLE_DRAWER,
// Action creators
back,
init,
navigate,
pop,
popToTop,
push,
reset,
replace,
setParams,
uri,
completeTransition,
openDrawer,
closeDrawer,
toggleDrawer,
};

View File

@@ -0,0 +1,9 @@
import {
BackAndroid as DeprecatedBackAndroid,
BackHandler as ModernBackHandler,
MaskedViewIOS,
} from 'react-native';
const BackHandler = ModernBackHandler || DeprecatedBackAndroid;
export { BackHandler, MaskedViewIOS };

View File

@@ -0,0 +1,6 @@
import React from 'react';
import { BackHandler, View } from 'react-native';
const MaskedViewIOS = () => <View>{this.props.children}</View>;
export { BackHandler, MaskedViewIOS };

184
src/StateUtils.js Normal file
View File

@@ -0,0 +1,184 @@
import invariant from './utils/invariant';
/**
* Utilities to perform atomic operation with navigate state and routes.
*
* ```javascript
* const state1 = {key: 'screen 1'};
* const state2 = NavigationStateUtils.push(state1, {key: 'screen 2'});
* ```
*/
const StateUtils = {
/**
* Gets a route by key. If the route isn't found, returns `null`.
*/
get(state, key) {
return state.routes.find(route => route.key === key) || null;
},
/**
* Returns the first index at which a given route's key can be found in the
* routes of the navigation state, or -1 if it is not present.
*/
indexOf(state, key) {
return state.routes.map(route => route.key).indexOf(key);
},
/**
* Returns `true` at which a given route's key can be found in the
* routes of the navigation state.
*/
has(state, key) {
return !!state.routes.some(route => route.key === key);
},
/**
* Pushes a new route into the navigation state.
* Note that this moves the index to the positon to where the last route in the
* stack is at.
*/
push(state, route) {
invariant(
StateUtils.indexOf(state, route.key) === -1,
'should not push route with duplicated key %s',
route.key
);
const routes = state.routes.slice();
routes.push(route);
return {
...state,
index: routes.length - 1,
routes,
};
},
/**
* Pops out a route from the navigation state.
* Note that this moves the index to the positon to where the last route in the
* stack is at.
*/
pop(state) {
if (state.index <= 0) {
// [Note]: Over-popping does not throw error. Instead, it will be no-op.
return state;
}
const routes = state.routes.slice(0, -1);
return {
...state,
index: routes.length - 1,
routes,
};
},
/**
* Sets the focused route of the navigation state by index.
*/
jumpToIndex(state, index) {
if (index === state.index) {
return state;
}
invariant(!!state.routes[index], 'invalid index %s to jump to', index);
return {
...state,
index,
};
},
/**
* Sets the focused route of the navigation state by key.
*/
jumpTo(state, key) {
const index = StateUtils.indexOf(state, key);
return StateUtils.jumpToIndex(state, index);
},
/**
* Sets the focused route to the previous route.
*/
back(state) {
const index = state.index - 1;
const route = state.routes[index];
return route ? StateUtils.jumpToIndex(state, index) : state;
},
/**
* Sets the focused route to the next route.
*/
forward(state) {
const index = state.index + 1;
const route = state.routes[index];
return route ? StateUtils.jumpToIndex(state, index) : state;
},
/**
* Replace a route by a key.
* Note that this moves the index to the positon to where the new route in the
* stack is at.
*/
replaceAt(state, key, route) {
const index = StateUtils.indexOf(state, key);
return StateUtils.replaceAtIndex(state, index, route);
},
/**
* Replace a route by a index.
* Note that this moves the index to the positon to where the new route in the
* stack is at.
*/
replaceAtIndex(state, index, route) {
invariant(
!!state.routes[index],
'invalid index %s for replacing route %s',
index,
route.key
);
if (state.routes[index] === route) {
return state;
}
const routes = state.routes.slice();
routes[index] = route;
return {
...state,
index,
routes,
};
},
/**
* Resets all routes.
* Note that this moves the index to the positon to where the last route in the
* stack is at if the param `index` isn't provided.
*/
reset(state, routes, index) {
invariant(
routes.length && Array.isArray(routes),
'invalid routes to replace'
);
const nextIndex = index === undefined ? routes.length - 1 : index;
if (state.routes.length === routes.length && state.index === nextIndex) {
const compare = (route, ii) => routes[ii] === route;
if (state.routes.every(compare)) {
return state;
}
}
invariant(!!routes[nextIndex], 'invalid index %s to reset', nextIndex);
return {
...state,
index: nextIndex,
routes,
};
},
};
export default StateUtils;

View File

@@ -0,0 +1,99 @@
import NavigationActions from '../NavigationActions';
describe('actions', () => {
const params = { foo: 'bar' };
const navigateAction = NavigationActions.navigate({ routeName: 'another' });
it('exports back action and type', () => {
expect(NavigationActions.back.toString()).toEqual(NavigationActions.BACK);
expect(NavigationActions.back()).toEqual({ type: NavigationActions.BACK });
expect(NavigationActions.back({ key: 'test' })).toEqual({
type: NavigationActions.BACK,
key: 'test',
});
});
it('exports init action and type', () => {
expect(NavigationActions.init.toString()).toEqual(NavigationActions.INIT);
expect(NavigationActions.init()).toEqual({ type: NavigationActions.INIT });
expect(NavigationActions.init({ params })).toEqual({
type: NavigationActions.INIT,
params,
});
});
it('exports navigate action and type', () => {
expect(NavigationActions.navigate.toString()).toEqual(
NavigationActions.NAVIGATE
);
expect(NavigationActions.navigate({ routeName: 'test' })).toEqual({
type: NavigationActions.NAVIGATE,
routeName: 'test',
});
expect(
NavigationActions.navigate({
routeName: 'test',
params,
action: navigateAction,
})
).toEqual({
type: NavigationActions.NAVIGATE,
routeName: 'test',
params,
action: {
type: NavigationActions.NAVIGATE,
routeName: 'another',
},
});
});
it('exports reset action and type', () => {
expect(NavigationActions.reset.toString()).toEqual(NavigationActions.RESET);
expect(NavigationActions.reset({ index: 0, actions: [] })).toEqual({
type: NavigationActions.RESET,
index: 0,
actions: [],
});
expect(
NavigationActions.reset({
index: 0,
key: 'test',
actions: [navigateAction],
})
).toEqual({
type: NavigationActions.RESET,
index: 0,
key: 'test',
actions: [
{
type: NavigationActions.NAVIGATE,
routeName: 'another',
},
],
});
});
it('exports setParams action and type', () => {
expect(NavigationActions.setParams.toString()).toEqual(
NavigationActions.SET_PARAMS
);
expect(
NavigationActions.setParams({
key: 'test',
params,
})
).toEqual({
type: NavigationActions.SET_PARAMS,
key: 'test',
params,
});
});
it('exports uri action and type', () => {
expect(NavigationActions.uri.toString()).toEqual(NavigationActions.URI);
expect(NavigationActions.uri({ uri: 'http://google.com' })).toEqual({
type: NavigationActions.URI,
uri: 'http://google.com',
});
});
});

View File

@@ -0,0 +1,201 @@
import React from 'react';
import 'react-native';
import renderer from 'react-test-renderer';
import NavigationActions from '../NavigationActions';
import StackNavigator from '../navigators/createStackNavigator';
const FooScreen = () => <div />;
const BarScreen = () => <div />;
const BazScreen = () => <div />;
const CarScreen = () => <div />;
const DogScreen = () => <div />;
const ElkScreen = () => <div />;
const NavigationContainer = StackNavigator(
{
foo: {
screen: FooScreen,
},
bar: {
screen: BarScreen,
},
baz: {
screen: BazScreen,
},
car: {
screen: CarScreen,
},
dog: {
screen: DogScreen,
},
elk: {
screen: ElkScreen,
},
},
{
initialRouteName: 'foo',
}
);
jest.useFakeTimers();
describe('NavigationContainer', () => {
describe('state.nav', () => {
it("should be preloaded with the router's initial state", () => {
const navigationContainer = renderer
.create(<NavigationContainer />)
.getInstance();
expect(navigationContainer.state.nav).toMatchObject({ index: 0 });
expect(navigationContainer.state.nav.routes).toBeInstanceOf(Array);
expect(navigationContainer.state.nav.routes.length).toBe(1);
expect(navigationContainer.state.nav.routes[0]).toMatchObject({
routeName: 'foo',
});
});
});
describe('dispatch', () => {
it('returns true when given a valid action', () => {
const navigationContainer = renderer
.create(<NavigationContainer />)
.getInstance();
jest.runOnlyPendingTimers();
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'bar' })
)
).toEqual(true);
});
it('returns false when given an invalid action', () => {
const navigationContainer = renderer
.create(<NavigationContainer />)
.getInstance();
jest.runOnlyPendingTimers();
expect(navigationContainer.dispatch(NavigationActions.back())).toEqual(
false
);
});
it('updates state.nav with an action by the next tick', () => {
const navigationContainer = renderer
.create(<NavigationContainer />)
.getInstance();
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'bar' })
)
).toEqual(true);
// Fake the passing of a tick
jest.runOnlyPendingTimers();
expect(navigationContainer.state.nav).toMatchObject({
index: 1,
routes: [{ routeName: 'foo' }, { routeName: 'bar' }],
});
});
it('does not discard actions when called twice in one tick', () => {
const navigationContainer = renderer
.create(<NavigationContainer />)
.getInstance();
const initialState = JSON.parse(
JSON.stringify(navigationContainer.state.nav)
);
// First dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'bar' })
)
).toEqual(true);
// Make sure that the test runner has NOT synchronously applied setState before the tick
expect(navigationContainer.state.nav).toMatchObject(initialState);
// Second dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'baz' })
)
).toEqual(true);
// Fake the passing of a tick
jest.runOnlyPendingTimers();
expect(navigationContainer.state.nav).toMatchObject({
index: 2,
routes: [
{ routeName: 'foo' },
{ routeName: 'bar' },
{ routeName: 'baz' },
],
});
});
it('does not discard actions when called more than 2 times in one tick', () => {
const navigationContainer = renderer
.create(<NavigationContainer />)
.getInstance();
const initialState = JSON.parse(
JSON.stringify(navigationContainer.state.nav)
);
// First dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'bar' })
)
).toEqual(true);
// Make sure that the test runner has NOT synchronously applied setState before the tick
expect(navigationContainer.state.nav).toMatchObject(initialState);
// Second dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'baz' })
)
).toEqual(true);
// Third dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'car' })
)
).toEqual(true);
// Fourth dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'dog' })
)
).toEqual(true);
// Fifth dispatch
expect(
navigationContainer.dispatch(
NavigationActions.navigate({ routeName: 'elk' })
)
).toEqual(true);
// Fake the passing of a tick
jest.runOnlyPendingTimers();
expect(navigationContainer.state.nav).toMatchObject({
index: 5,
routes: [
{ routeName: 'foo' },
{ routeName: 'bar' },
{ routeName: 'baz' },
{ routeName: 'car' },
{ routeName: 'dog' },
{ routeName: 'elk' },
],
});
});
});
});

View File

@@ -0,0 +1,239 @@
import NavigationStateUtils from '../StateUtils';
const routeName = 'Anything';
describe('StateUtils', () => {
// Getters
it('gets route', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }],
};
expect(NavigationStateUtils.get(state, 'a')).toEqual({
key: 'a',
routeName,
});
expect(NavigationStateUtils.get(state, 'b')).toBe(null);
});
it('gets route index', () => {
const state = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.indexOf(state, 'a')).toBe(0);
expect(NavigationStateUtils.indexOf(state, 'b')).toBe(1);
expect(NavigationStateUtils.indexOf(state, 'c')).toBe(-1);
});
it('has a route', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.has(state, 'b')).toBe(true);
expect(NavigationStateUtils.has(state, 'c')).toBe(false);
});
// Push
it('pushes a route', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }],
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.push(state, { key: 'b', routeName })).toEqual(
newState
);
});
it('does not push duplicated route', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }],
};
expect(() =>
NavigationStateUtils.push(state, { key: 'a', routeName })
).toThrow();
});
// Pop
it('pops route', () => {
const state = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 0,
routes: [{ key: 'a', routeName }],
};
expect(NavigationStateUtils.pop(state)).toEqual(newState);
});
it('does not pop route if not applicable', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }],
};
expect(NavigationStateUtils.pop(state)).toBe(state);
});
// Jump
it('jumps to new index', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.jumpToIndex(state, 0)).toBe(state);
expect(NavigationStateUtils.jumpToIndex(state, 1)).toEqual(newState);
});
it('throws if jumps to invalid index', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(() => NavigationStateUtils.jumpToIndex(state, 2)).toThrow();
});
it('jumps to new key', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.jumpTo(state, 'a')).toBe(state);
expect(NavigationStateUtils.jumpTo(state, 'b')).toEqual(newState);
});
it('throws if jumps to invalid key', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(() => NavigationStateUtils.jumpTo(state, 'c')).toThrow();
});
it('move backwards', () => {
const state = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.back(state)).toEqual(newState);
expect(NavigationStateUtils.back(newState)).toBe(newState);
});
it('move forwards', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(NavigationStateUtils.forward(state)).toEqual(newState);
expect(NavigationStateUtils.forward(newState)).toBe(newState);
});
// Replace
it('Replaces by key', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
};
expect(
NavigationStateUtils.replaceAt(state, 'b', { key: 'c', routeName })
).toEqual(newState);
});
it('Replaces by index', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 1,
routes: [{ key: 'a', routeName }, { key: 'c', routeName }],
};
expect(
NavigationStateUtils.replaceAtIndex(state, 1, { key: 'c', routeName })
).toEqual(newState);
});
it('Returns the state if index matches the route', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
expect(
NavigationStateUtils.replaceAtIndex(state, 1, state.routes[1])
).toEqual(state);
});
// Reset
it('Resets routes', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 1,
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
};
expect(
NavigationStateUtils.reset(state, [
{ key: 'x', routeName },
{ key: 'y', routeName },
])
).toEqual(newState);
expect(() => {
NavigationStateUtils.reset(state, []);
}).toThrow();
});
it('Resets routes with index', () => {
const state = {
index: 0,
routes: [{ key: 'a', routeName }, { key: 'b', routeName }],
};
const newState = {
index: 0,
routes: [{ key: 'x', routeName }, { key: 'y', routeName }],
};
expect(
NavigationStateUtils.reset(
state,
[{ key: 'x', routeName }, { key: 'y', routeName }],
0
)
).toEqual(newState);
expect(() => {
NavigationStateUtils.reset(
state,
[{ key: 'x', routeName }, { key: 'y', routeName }],
100
);
}).toThrow();
});
});

View File

@@ -0,0 +1,118 @@
import NavigationActions from '../NavigationActions';
import addNavigationHelpers from '../addNavigationHelpers';
const dummyEventSubscriber = (name: string, handler: (*) => void) => ({
remove: () => {},
});
describe('addNavigationHelpers', () => {
it('handles Back action', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { key: 'A', routeName: 'Home' },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).goBack('A')
).toEqual(true);
expect(mockedDispatch).toBeCalledWith({
type: NavigationActions.BACK,
key: 'A',
});
expect(mockedDispatch.mock.calls.length).toBe(1);
});
it('handles Back action when the key is not defined', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { routeName: 'Home' },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).goBack()
).toEqual(true);
expect(mockedDispatch).toBeCalledWith({ type: NavigationActions.BACK });
expect(mockedDispatch.mock.calls.length).toBe(1);
});
it('handles Navigate action', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { routeName: 'Home' },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).navigate('Profile', { name: 'Matt' })
).toEqual(true);
expect(mockedDispatch).toBeCalledWith({
type: NavigationActions.NAVIGATE,
params: { name: 'Matt' },
routeName: 'Profile',
});
expect(mockedDispatch.mock.calls.length).toBe(1);
});
it('handles SetParams action', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { key: 'B', routeName: 'Settings' },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).setParams({ notificationsEnabled: 'yes' })
).toEqual(true);
expect(mockedDispatch).toBeCalledWith({
type: NavigationActions.SET_PARAMS,
key: 'B',
params: { notificationsEnabled: 'yes' },
});
expect(mockedDispatch.mock.calls.length).toBe(1);
});
it('handles GetParams action', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { key: 'B', routeName: 'Settings', params: { name: 'Peter' } },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).getParam('name', 'Brent')
).toEqual('Peter');
});
it('handles GetParams action with default param', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { key: 'B', routeName: 'Settings' },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).getParam('name', 'Brent')
).toEqual('Brent');
});
it('handles GetParams action with param value as null', () => {
const mockedDispatch = jest
.fn(() => false)
.mockImplementationOnce(() => true);
expect(
addNavigationHelpers({
state: { key: 'B', routeName: 'Settings', params: { name: null } },
dispatch: mockedDispatch,
addListener: dummyEventSubscriber,
}).getParam('name')
).toEqual(null);
});
});

View File

@@ -0,0 +1,460 @@
import getChildEventSubscriber from '../getChildEventSubscriber';
test('child action events only flow when focused', () => {
const parentSubscriber = jest.fn();
const emitParentAction = payload => {
parentSubscriber.mock.calls.forEach(subs => {
if (subs[0] === payload.type) {
subs[1](payload);
}
});
};
const subscriptionRemove = () => {};
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const testState = {
key: 'foo',
routeName: 'FooRoute',
routes: [{ key: 'key0' }, { key: 'key1' }],
index: 0,
isTransitioning: false,
};
const focusedTestState = {
...testState,
index: 1,
};
const childActionHandler = jest.fn();
const childWillFocusHandler = jest.fn();
const childDidFocusHandler = jest.fn();
childEventSubscriber('action', childActionHandler);
childEventSubscriber('willFocus', childWillFocusHandler);
childEventSubscriber('didFocus', childDidFocusHandler);
emitParentAction({
type: 'action',
state: focusedTestState,
lastState: testState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
emitParentAction({
type: 'action',
state: focusedTestState,
lastState: focusedTestState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(1);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
});
test('grandchildren subscription', () => {
const grandParentSubscriber = jest.fn();
const emitGrandParentAction = payload => {
grandParentSubscriber.mock.calls.forEach(subs => {
if (subs[0] === payload.type) {
subs[1](payload);
}
});
};
const subscriptionRemove = () => {};
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const parentSubscriber = getChildEventSubscriber(
grandParentSubscriber,
'parent'
).addListener;
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const parentBlurState = {
key: 'foo',
routeName: 'FooRoute',
routes: [
{ key: 'aunt' },
{
key: 'parent',
routes: [{ key: 'key0' }, { key: 'key1' }],
index: 1,
isTransitioning: false,
},
],
index: 0,
isTransitioning: false,
};
const parentTransitionState = {
...parentBlurState,
index: 1,
isTransitioning: true,
};
const parentFocusState = {
...parentTransitionState,
isTransitioning: false,
};
const childActionHandler = jest.fn();
const childWillFocusHandler = jest.fn();
const childDidFocusHandler = jest.fn();
childEventSubscriber('action', childActionHandler);
childEventSubscriber('willFocus', childWillFocusHandler);
childEventSubscriber('didFocus', childDidFocusHandler);
emitGrandParentAction({
type: 'action',
state: parentTransitionState,
lastState: parentBlurState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(0);
emitGrandParentAction({
type: 'action',
state: parentFocusState,
lastState: parentTransitionState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
});
test('grandchildren transitions', () => {
const grandParentSubscriber = jest.fn();
const emitGrandParentAction = payload => {
grandParentSubscriber.mock.calls.forEach(subs => {
if (subs[0] === payload.type) {
subs[1](payload);
}
});
};
const subscriptionRemove = () => {};
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const parentSubscriber = getChildEventSubscriber(
grandParentSubscriber,
'parent'
).addListener;
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const makeFakeState = (childIndex, childIsTransitioning) => ({
index: 1,
isTransitioning: false,
routes: [
{ key: 'nothing' },
{
key: 'parent',
index: childIndex,
isTransitioning: childIsTransitioning,
routes: [{ key: 'key0' }, { key: 'key1' }, { key: 'key2' }],
},
],
});
const blurredState = makeFakeState(0, false);
const transitionState = makeFakeState(1, true);
const focusState = makeFakeState(1, false);
const transition2State = makeFakeState(2, true);
const blurred2State = makeFakeState(2, false);
const childActionHandler = jest.fn();
const childWillFocusHandler = jest.fn();
const childDidFocusHandler = jest.fn();
const childWillBlurHandler = jest.fn();
const childDidBlurHandler = jest.fn();
childEventSubscriber('action', childActionHandler);
childEventSubscriber('willFocus', childWillFocusHandler);
childEventSubscriber('didFocus', childDidFocusHandler);
childEventSubscriber('willBlur', childWillBlurHandler);
childEventSubscriber('didBlur', childDidBlurHandler);
emitGrandParentAction({
type: 'action',
state: transitionState,
lastState: blurredState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(0);
emitGrandParentAction({
type: 'action',
state: focusState,
lastState: transitionState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
emitGrandParentAction({
type: 'action',
state: focusState,
lastState: focusState,
action: { type: 'TestAction' },
});
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
expect(childActionHandler.mock.calls.length).toBe(1);
emitGrandParentAction({
type: 'action',
state: transition2State,
lastState: focusState,
action: { type: 'CauseWillBlurAction' },
});
expect(childWillBlurHandler.mock.calls.length).toBe(1);
expect(childDidBlurHandler.mock.calls.length).toBe(0);
expect(childActionHandler.mock.calls.length).toBe(1);
emitGrandParentAction({
type: 'action',
state: blurred2State,
lastState: transition2State,
action: { type: 'CauseDidBlurAction' },
});
expect(childWillBlurHandler.mock.calls.length).toBe(1);
expect(childDidBlurHandler.mock.calls.length).toBe(1);
expect(childActionHandler.mock.calls.length).toBe(1);
});
test('grandchildren pass through transitions', () => {
const grandParentSubscriber = jest.fn();
const emitGrandParentAction = payload => {
grandParentSubscriber.mock.calls.forEach(subs => {
if (subs[0] === payload.type) {
subs[1](payload);
}
});
};
const subscriptionRemove = () => {};
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const parentSubscriber = getChildEventSubscriber(
grandParentSubscriber,
'parent'
).addListener;
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const makeFakeState = (childIndex, childIsTransitioning) => ({
index: childIndex,
isTransitioning: childIsTransitioning,
routes: [
{ key: 'nothing' },
{
key: 'parent',
index: 1,
isTransitioning: false,
routes: [{ key: 'key0' }, { key: 'key1' }, { key: 'key2' }],
},
].slice(0, childIndex + 1),
});
const blurredState = makeFakeState(0, false);
const transitionState = makeFakeState(1, true);
const focusState = makeFakeState(1, false);
const transition2State = makeFakeState(0, true);
const blurred2State = makeFakeState(0, false);
const childActionHandler = jest.fn();
const childWillFocusHandler = jest.fn();
const childDidFocusHandler = jest.fn();
const childWillBlurHandler = jest.fn();
const childDidBlurHandler = jest.fn();
childEventSubscriber('action', childActionHandler);
childEventSubscriber('willFocus', childWillFocusHandler);
childEventSubscriber('didFocus', childDidFocusHandler);
childEventSubscriber('willBlur', childWillBlurHandler);
childEventSubscriber('didBlur', childDidBlurHandler);
emitGrandParentAction({
type: 'action',
state: transitionState,
lastState: blurredState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(0);
emitGrandParentAction({
type: 'action',
state: focusState,
lastState: transitionState,
action: { type: 'FooAction' },
});
expect(childActionHandler.mock.calls.length).toBe(0);
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
emitGrandParentAction({
type: 'action',
state: focusState,
lastState: focusState,
action: { type: 'TestAction' },
});
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
expect(childActionHandler.mock.calls.length).toBe(1);
emitGrandParentAction({
type: 'action',
state: transition2State,
lastState: focusState,
action: { type: 'CauseWillBlurAction' },
});
expect(childWillBlurHandler.mock.calls.length).toBe(1);
expect(childDidBlurHandler.mock.calls.length).toBe(0);
expect(childActionHandler.mock.calls.length).toBe(1);
emitGrandParentAction({
type: 'action',
state: blurred2State,
lastState: transition2State,
action: { type: 'CauseDidBlurAction' },
});
expect(childWillBlurHandler.mock.calls.length).toBe(1);
expect(childDidBlurHandler.mock.calls.length).toBe(1);
expect(childActionHandler.mock.calls.length).toBe(1);
});
test('child focus with transition', () => {
const parentSubscriber = jest.fn();
const emitParentAction = payload => {
parentSubscriber.mock.calls.forEach(subs => {
if (subs[0] === payload.type) {
subs[1](payload);
}
});
};
const subscriptionRemove = () => {};
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const randomAction = { type: 'FooAction' };
const testState = {
key: 'foo',
routeName: 'FooRoute',
routes: [{ key: 'key0' }, { key: 'key1' }],
index: 0,
isTransitioning: false,
};
const childWillFocusHandler = jest.fn();
const childDidFocusHandler = jest.fn();
const childWillBlurHandler = jest.fn();
const childDidBlurHandler = jest.fn();
childEventSubscriber('willFocus', childWillFocusHandler);
childEventSubscriber('didFocus', childDidFocusHandler);
childEventSubscriber('willBlur', childWillBlurHandler);
childEventSubscriber('didBlur', childDidBlurHandler);
emitParentAction({
type: 'didFocus',
action: randomAction,
lastState: testState,
state: testState,
});
emitParentAction({
type: 'action',
action: randomAction,
lastState: testState,
state: {
...testState,
index: 1,
isTransitioning: true,
},
});
expect(childWillFocusHandler.mock.calls.length).toBe(1);
emitParentAction({
type: 'action',
action: randomAction,
lastState: {
...testState,
index: 1,
isTransitioning: true,
},
state: {
...testState,
index: 1,
isTransitioning: false,
},
});
expect(childDidFocusHandler.mock.calls.length).toBe(1);
emitParentAction({
type: 'action',
action: randomAction,
lastState: {
...testState,
index: 1,
isTransitioning: false,
},
state: {
...testState,
index: 0,
isTransitioning: true,
},
});
expect(childWillBlurHandler.mock.calls.length).toBe(1);
emitParentAction({
type: 'action',
action: randomAction,
lastState: {
...testState,
index: 0,
isTransitioning: true,
},
state: {
...testState,
index: 0,
isTransitioning: false,
},
});
expect(childDidBlurHandler.mock.calls.length).toBe(1);
});
test('child focus with immediate transition', () => {
const parentSubscriber = jest.fn();
const emitParentAction = payload => {
parentSubscriber.mock.calls.forEach(subs => {
if (subs[0] === payload.type) {
subs[1](payload);
}
});
};
const subscriptionRemove = () => {};
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
.addListener;
const randomAction = { type: 'FooAction' };
const testState = {
key: 'foo',
routeName: 'FooRoute',
routes: [{ key: 'key0' }, { key: 'key1' }],
index: 0,
isTransitioning: false,
};
const childWillFocusHandler = jest.fn();
const childDidFocusHandler = jest.fn();
const childWillBlurHandler = jest.fn();
const childDidBlurHandler = jest.fn();
childEventSubscriber('willFocus', childWillFocusHandler);
childEventSubscriber('didFocus', childDidFocusHandler);
childEventSubscriber('willBlur', childWillBlurHandler);
childEventSubscriber('didBlur', childDidBlurHandler);
emitParentAction({
type: 'didFocus',
action: randomAction,
lastState: testState,
state: testState,
});
emitParentAction({
type: 'action',
action: randomAction,
lastState: testState,
state: {
...testState,
index: 1,
},
});
expect(childWillFocusHandler.mock.calls.length).toBe(1);
expect(childDidFocusHandler.mock.calls.length).toBe(1);
emitParentAction({
type: 'action',
action: randomAction,
lastState: {
...testState,
index: 1,
},
state: {
...testState,
index: 0,
},
});
expect(childWillBlurHandler.mock.calls.length).toBe(1);
expect(childDidBlurHandler.mock.calls.length).toBe(1);
});

View File

@@ -0,0 +1,93 @@
/* Helpers for navigation */
import NavigationActions from './NavigationActions';
import invariant from './utils/invariant';
export default function(navigation) {
return {
...navigation,
goBack: key => {
let actualizedKey = key;
if (key === undefined && navigation.state.key) {
invariant(
typeof navigation.state.key === 'string',
'key should be a string'
);
actualizedKey = navigation.state.key;
}
return navigation.dispatch(
NavigationActions.back({ key: actualizedKey })
);
},
navigate: (navigateTo, params, action) => {
if (typeof navigateTo === 'string') {
return navigation.dispatch(
NavigationActions.navigate({ routeName: navigateTo, params, action })
);
}
invariant(
typeof navigateTo === 'object',
'Must navigateTo an object or a string'
);
invariant(
params == null,
'Params must not be provided to .navigate() when specifying an object'
);
invariant(
action == null,
'Child action must not be provided to .navigate() when specifying an object'
);
return navigation.dispatch(NavigationActions.navigate(navigateTo));
},
pop: (n, params) =>
navigation.dispatch(
NavigationActions.pop({ n, immediate: params && params.immediate })
),
popToTop: params =>
navigation.dispatch(
NavigationActions.popToTop({ immediate: params && params.immediate })
),
/**
* For updating current route params. For example the nav bar title and
* buttons are based on the route params.
* This means `setParams` can be used to update nav bar for example.
*/
setParams: params => {
invariant(
navigation.state.key && typeof navigation.state.key === 'string',
'setParams cannot be called by root navigator'
);
const key = navigation.state.key;
return navigation.dispatch(NavigationActions.setParams({ params, key }));
},
getParam: (paramName, defaultValue) => {
const params = navigation.state.params;
if (params && paramName in params) {
return params[paramName];
}
return defaultValue;
},
push: (routeName, params, action) =>
navigation.dispatch(
NavigationActions.push({ routeName, params, action })
),
replace: (routeName, params, action) =>
navigation.dispatch(
NavigationActions.replace({
routeName,
params,
action,
key: navigation.state.key,
})
),
openDrawer: () => navigation.dispatch(NavigationActions.openDrawer()),
closeDrawer: () => navigation.dispatch(NavigationActions.closeDrawer()),
toggleDrawer: () => navigation.dispatch(NavigationActions.toggleDrawer()),
};
}

View File

@@ -0,0 +1,231 @@
import React from 'react';
import { Linking } from 'react-native';
import { BackHandler } from './PlatformHelpers';
import NavigationActions from './NavigationActions';
import addNavigationHelpers from './addNavigationHelpers';
import invariant from './utils/invariant';
/**
* Create an HOC that injects the navigation and manages the navigation state
* in case it's not passed from above.
* This allows to use e.g. the StackNavigator and TabNavigator as root-level
* components.
*/
export default function createNavigationContainer(Component) {
class NavigationContainer extends React.Component {
subs = null;
static router = Component.router;
static navigationOptions = null;
_actionEventSubscribers = new Set();
constructor(props) {
super(props);
this._validateProps(props);
this._initialAction = NavigationActions.init();
if (this._isStateful()) {
this.subs = BackHandler.addEventListener('hardwareBackPress', () => {
if (!this._isMounted) {
this.subs && this.subs.remove();
} else {
// dispatch returns true if the action results in a state change,
// and false otherwise. This maps well to what BackHandler expects
// from a callback -- true if handled, false if not handled
return this.dispatch(NavigationActions.back());
}
});
}
this.state = {
nav: this._isStateful()
? Component.router.getStateForAction(this._initialAction)
: null,
};
}
_isStateful() {
return !this.props.navigation;
}
_validateProps(props) {
if (this._isStateful()) {
return;
}
const { navigation, screenProps, ...containerProps } = props;
const keys = Object.keys(containerProps);
if (keys.length !== 0) {
throw new Error(
'This navigator has both navigation and container props, so it is ' +
`unclear if it should own its own state. Remove props: "${keys.join(
', '
)}" ` +
'if the navigator should get its state from the navigation prop. If the ' +
'navigator should maintain its own state, do not pass a navigation prop.'
);
}
}
_urlToPathAndParams(url) {
const params = {};
const delimiter = this.props.uriPrefix || '://';
let path = url.split(delimiter)[1];
if (typeof path === 'undefined') {
path = url;
} else if (path === '') {
path = '/';
}
return {
path,
params,
};
}
_handleOpenURL = ({ url }) => {
const parsedUrl = this._urlToPathAndParams(url);
if (parsedUrl) {
const { path, params } = parsedUrl;
const action = Component.router.getActionForPathAndParams(path, params);
if (action) {
this.dispatch(action);
}
}
};
_onNavigationStateChange(prevNav, nav, action) {
if (
typeof this.props.onNavigationStateChange === 'undefined' &&
this._isStateful() &&
!!process.env.REACT_NAV_LOGGING
) {
/* eslint-disable no-console */
if (console.group) {
console.group('Navigation Dispatch: ');
console.log('Action: ', action);
console.log('New State: ', nav);
console.log('Last State: ', prevNav);
console.groupEnd();
} else {
console.log('Navigation Dispatch: ', {
action,
newState: nav,
lastState: prevNav,
});
}
/* eslint-enable no-console */
return;
}
if (typeof this.props.onNavigationStateChange === 'function') {
this.props.onNavigationStateChange(prevNav, nav, action);
}
}
componentWillReceiveProps(nextProps) {
this._validateProps(nextProps);
}
componentDidUpdate() {
// Clear cached _nav every tick
if (this._nav === this.state.nav) {
this._nav = null;
}
}
componentDidMount() {
this._isMounted = true;
if (!this._isStateful()) {
return;
}
Linking.addEventListener('url', this._handleOpenURL);
Linking.getInitialURL().then(url => url && this._handleOpenURL({ url }));
this._actionEventSubscribers.forEach(subscriber =>
subscriber({
type: 'action',
action: this._initialAction,
state: this.state.nav,
lastState: null,
})
);
}
componentWillUnmount() {
this._isMounted = false;
Linking.removeEventListener('url', this._handleOpenURL);
this.subs && this.subs.remove();
}
// Per-tick temporary storage for state.nav
dispatch = action => {
if (!this._isStateful()) {
return false;
}
this._nav = this._nav || this.state.nav;
const oldNav = this._nav;
invariant(oldNav, 'should be set in constructor if stateful');
const nav = Component.router.getStateForAction(action, oldNav);
const dispatchActionEvents = () => {
this._actionEventSubscribers.forEach(subscriber =>
subscriber({
type: 'action',
action,
state: nav,
lastState: oldNav,
})
);
};
if (nav && nav !== oldNav) {
// Cache updates to state.nav during the tick to ensure that subsequent calls will not discard this change
this._nav = nav;
this.setState({ nav }, () => {
this._onNavigationStateChange(oldNav, nav, action);
dispatchActionEvents();
});
return true;
} else {
dispatchActionEvents();
}
return false;
};
render() {
let navigation = this.props.navigation;
if (this._isStateful()) {
const nav = this.state.nav;
invariant(nav, 'should be set in constructor if stateful');
if (!this._navigation || this._navigation.state !== nav) {
this._navigation = addNavigationHelpers({
dispatch: this.dispatch,
state: nav,
addListener: (eventName, handler) => {
if (eventName !== 'action') {
return { remove: () => {} };
}
this._actionEventSubscribers.add(handler);
return {
remove: () => {
this._actionEventSubscribers.delete(handler);
},
};
},
});
}
navigation = this._navigation;
}
invariant(navigation, 'failed to get navigation');
return <Component {...this.props} navigation={navigation} />;
}
}
return NavigationContainer;
}

View File

@@ -0,0 +1,158 @@
/*
* This is used to extract one children's worth of events from a stream of navigation action events
*
* Based on the 'action' events that get fired for this navigation state, this utility will fire
* focus and blur events for this child
*/
export default function getChildEventSubscriber(addListener, key) {
const actionSubscribers = new Set();
const willFocusSubscribers = new Set();
const didFocusSubscribers = new Set();
const willBlurSubscribers = new Set();
const didBlurSubscribers = new Set();
const removeAll = () => {
[
actionSubscribers,
willFocusSubscribers,
didFocusSubscribers,
willBlurSubscribers,
didBlurSubscribers,
].forEach(set => set.clear());
upstreamSubscribers.forEach(subs => subs && subs.remove());
};
const getChildSubscribers = evtName => {
switch (evtName) {
case 'action':
return actionSubscribers;
case 'willFocus':
return willFocusSubscribers;
case 'didFocus':
return didFocusSubscribers;
case 'willBlur':
return willBlurSubscribers;
case 'didBlur':
return didBlurSubscribers;
default:
return null;
}
};
const emit = (type, payload) => {
const payloadWithType = { ...payload, type };
const subscribers = getChildSubscribers(type);
subscribers &&
subscribers.forEach(subs => {
subs(payloadWithType);
});
};
// lastEmittedEvent keeps track of focus state for one route. First we assume
// we are blurred. If we are focused on initialization, the first 'action'
// event will cause onFocus+willFocus events because we had previously been
// considered blurred
let lastEmittedEvent = 'didBlur';
const upstreamEvents = [
'willFocus',
'didFocus',
'willBlur',
'didBlur',
'action',
];
const upstreamSubscribers = upstreamEvents.map(eventName =>
addListener(eventName, payload => {
const { state, lastState, action } = payload;
const lastRoutes = lastState && lastState.routes;
const routes = state && state.routes;
const lastFocusKey =
lastState && lastState.routes && lastState.routes[lastState.index].key;
const focusKey = routes && routes[state.index].key;
const isChildFocused = focusKey === key;
const lastRoute =
lastRoutes && lastRoutes.find(route => route.key === key);
const newRoute = routes && routes.find(route => route.key === key);
const childPayload = {
context: `${key}:${action.type}_${payload.context || 'Root'}`,
state: newRoute,
lastState: lastRoute,
action,
type: eventName,
};
const isTransitioning = !!state && !!state.transitioningFromKey;
const previouslyLastEmittedEvent = lastEmittedEvent;
if (lastEmittedEvent === 'didBlur') {
// The child is currently blurred. Look for willFocus conditions
if (eventName === 'willFocus' && isChildFocused) {
emit((lastEmittedEvent = 'willFocus'), childPayload);
} else if (eventName === 'action' && isChildFocused) {
emit((lastEmittedEvent = 'willFocus'), childPayload);
}
}
if (lastEmittedEvent === 'willFocus') {
// We are currently mid-focus. Look for didFocus conditions.
// If state.isTransitioning is false, this child event happens immediately after willFocus
if (eventName === 'didFocus' && isChildFocused && !isTransitioning) {
emit((lastEmittedEvent = 'didFocus'), childPayload);
} else if (
eventName === 'action' &&
isChildFocused &&
!isTransitioning
) {
emit((lastEmittedEvent = 'didFocus'), childPayload);
}
}
if (lastEmittedEvent === 'didFocus') {
// The child is currently focused. Look for blurring events
if (!isChildFocused) {
// The child is no longer focused within this navigation state
emit((lastEmittedEvent = 'willBlur'), childPayload);
} else if (eventName === 'willBlur') {
// The parent is getting a willBlur event
emit((lastEmittedEvent = 'willBlur'), childPayload);
} else if (
eventName === 'action' &&
previouslyLastEmittedEvent === 'didFocus'
) {
// While focused, pass action events to children for grandchildren focus
emit('action', childPayload);
}
}
if (lastEmittedEvent === 'willBlur') {
// The child is mid-blur. Wait for transition to end
if (eventName === 'action' && !isChildFocused && !isTransitioning) {
// The child is done blurring because transitioning is over, or isTransitioning
// never began and didBlur fires immediately after willBlur
emit((lastEmittedEvent = 'didBlur'), childPayload);
} else if (eventName === 'didBlur') {
// Pass through the parent didBlur event if it happens
emit((lastEmittedEvent = 'didBlur'), childPayload);
}
}
})
);
return {
removeAll,
addListener(eventName, eventHandler) {
const subscribers = getChildSubscribers(eventName);
if (!subscribers) {
throw new Error(`Invalid event name "${eventName}"`);
}
subscribers.add(eventHandler);
const remove = () => {
subscribers.delete(eventHandler);
};
return { remove };
},
};
}

View File

@@ -0,0 +1,71 @@
import React from 'react';
import { Dimensions, Platform, ScrollView } from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import createNavigator from './createNavigator';
import createNavigationContainer from '../createNavigationContainer';
import DrawerRouter from '../routers/DrawerRouter';
import DrawerScreen from '../views/Drawer/DrawerScreen';
import DrawerView from '../views/Drawer/DrawerView';
import DrawerItems from '../views/Drawer/DrawerNavigatorItems';
// A stack navigators props are the intersection between
// the base navigator props (navgiation, screenProps, etc)
// and the view's props
const defaultContentComponent = props => (
<ScrollView alwaysBounceVertical={false}>
<SafeAreaView forceInset={{ top: 'always', horizontal: 'never' }}>
<DrawerItems {...props} />
</SafeAreaView>
</ScrollView>
);
const DefaultDrawerConfig = {
drawerWidth: () => {
/*
* Default drawer width is screen width - header height
* with a max width of 280 on mobile and 320 on tablet
* https://material.io/guidelines/patterns/navigation-drawer.html
*/
const { height, width } = Dimensions.get('window');
const smallerAxisSize = Math.min(height, width);
const isLandscape = width > height;
const isTablet = smallerAxisSize >= 600;
const appBarHeight = Platform.OS === 'ios' ? (isLandscape ? 32 : 44) : 56;
const maxWidth = isTablet ? 320 : 280;
return Math.min(smallerAxisSize - appBarHeight, maxWidth);
},
contentComponent: defaultContentComponent,
drawerPosition: 'left',
drawerBackgroundColor: 'white',
useNativeAnimations: true,
};
const DrawerNavigator = (routeConfigs, config = {}) => {
const mergedConfig = { ...DefaultDrawerConfig, ...config };
const {
order,
paths,
initialRouteName,
backBehavior,
...drawerConfig
} = mergedConfig;
const routerConfig = {
order,
paths,
initialRouteName,
backBehavior,
};
const drawerRouter = DrawerRouter(routeConfigs, routerConfig);
const navigator = createNavigator(DrawerView, drawerRouter, drawerConfig);
return createNavigationContainer(navigator);
};
export default DrawerNavigator;

View File

@@ -0,0 +1,33 @@
import React, { Component } from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
import DrawerNavigator from '../DrawerNavigator';
class HomeScreen extends Component {
static navigationOptions = ({ navigation }) => ({
title: `Welcome ${
navigation.state.params ? navigation.state.params.user : 'anonymous'
}`,
gesturesEnabled: true,
});
render() {
return null;
}
}
const routeConfig = {
Home: {
screen: HomeScreen,
},
};
describe('DrawerNavigator', () => {
it('renders successfully', () => {
const MyDrawerNavigator = DrawerNavigator(routeConfig);
const rendered = renderer.create(<MyDrawerNavigator />).toJSON();
expect(rendered).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,54 @@
import React, { Component } from 'react';
import { StyleSheet, View } from 'react-native';
import renderer from 'react-test-renderer';
import StackNavigator from '../createStackNavigator';
const styles = StyleSheet.create({
header: {
opacity: 0.5,
},
});
class HomeScreen extends Component {
static navigationOptions = ({ navigation }) => ({
title: `Welcome ${
navigation.state.params ? navigation.state.params.user : 'anonymous'
}`,
gesturesEnabled: true,
headerStyle: [{ backgroundColor: 'red' }, styles.header],
});
render() {
return null;
}
}
const routeConfig = {
Home: {
screen: HomeScreen,
},
};
describe('StackNavigator', () => {
it('renders successfully', () => {
const MyStackNavigator = StackNavigator(routeConfig);
const rendered = renderer.create(<MyStackNavigator />).toJSON();
expect(rendered).toMatchSnapshot();
});
it('applies correct values when headerRight is present', () => {
const MyStackNavigator = StackNavigator({
Home: {
screen: HomeScreen,
navigationOptions: {
headerRight: <View />,
},
},
});
const rendered = renderer.create(<MyStackNavigator />).toJSON();
expect(rendered).toMatchSnapshot();
});
});

View File

@@ -1,20 +0,0 @@
import React from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
import { createSwitchNavigator } from '@react-navigation/core';
import { createAppContainer } from '@react-navigation/native';
const A = () => <View />;
const B = () => <View />;
const routeConfig = { A, B };
describe('SwitchNavigator', () => {
it('renders successfully', () => {
const MySwitchNavigator = createSwitchNavigator(routeConfig);
const App = createAppContainer(MySwitchNavigator);
const rendered = renderer.create(<App />).toJSON();
expect(rendered).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,33 @@
import React, { Component } from 'react';
import { View } from 'react-native';
import renderer from 'react-test-renderer';
import TabNavigator from '../createTabNavigator';
class HomeScreen extends Component {
static navigationOptions = ({ navigation }) => ({
title: `Welcome ${
navigation.state.params ? navigation.state.params.user : 'anonymous'
}`,
gesturesEnabled: true,
});
render() {
return null;
}
}
const routeConfig = {
Home: {
screen: HomeScreen,
},
};
describe('TabNavigator', () => {
it('renders successfully', () => {
const MyTabNavigator = TabNavigator(routeConfig);
const rendered = renderer.create(<MyTabNavigator />).toJSON();
expect(rendered).toMatchSnapshot();
});
});

Some files were not shown because too many files have changed in this diff Show More