├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── LICENSE.md
├── README.md
├── example
├── .babelrc
├── .buckconfig
├── .eslintrc
├── .watchmanconfig
├── App.js
├── README.md
├── app.json
├── package.json
├── rn-cli.config.js
├── src
│ └── Fade.js
└── yarn.lock
├── jest-setup.js
├── package.json
├── src
├── index.js
├── navigators
│ ├── __tests__
│ │ └── switchRouter-test.js
│ ├── createContainedSwitchNavigator.js
│ ├── createSwitchNavigator.js
│ └── switchRouter.js
├── utils
│ └── withTransition.js
└── views
│ ├── FadeTransition.js
│ └── Transitioner.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react-native"]
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 |
10 | # change these settings to your own preference
11 | indent_style = space
12 | indent_size = 2
13 |
14 | # we recommend you to keep these unchanged
15 | end_of_line = lf
16 | charset = utf-8
17 | trim_trailing_whitespace = true
18 | insert_final_newline = true
19 |
20 | [*.md]
21 | trim_trailing_whitespace = false
22 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | jest-setup.js
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": ["airbnb", "prettier"],
4 | "env": {
5 | "jest": true
6 | },
7 | "globals": {
8 | "__DEV__": false
9 | },
10 | "rules": {
11 | "react/jsx-filename-extension": [1, { "extensions": [".js"] }],
12 | "react/no-unescaped-entities": ["error", { "forbid": [">", "}"] }],
13 | "react/destructuring-assignment": [
14 | "on",
15 | "always",
16 | { "ignoreClassFields": true }
17 | ],
18 | "react/prop-types": [0],
19 | "no-underscore-dangle": "off",
20 | "no-console": "off"
21 | },
22 | "settings": {
23 | "import/resolver": {
24 | "node": {
25 | "extensions": [".js", ".ios.js", ".android.js"]
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # XDE
6 | .expo/
7 |
8 | # VSCode
9 | .vscode/
10 | tsconfig.json
11 | jsconfig.json
12 |
13 | # Xcode
14 | #
15 | build/
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 | xcuserdata
25 | *.xccheckout
26 | *.moved-aside
27 | DerivedData
28 | *.hmap
29 | *.ipa
30 | *.xcuserstate
31 | project.xcworkspace
32 |
33 | # Android/IJ
34 | #
35 | .idea
36 | .gradle
37 | local.properties
38 |
39 | # node.js
40 | #
41 | node_modules/
42 | npm-debug.log
43 | yarn-debug.log
44 | yarn-error.log
45 |
46 | # BUCK
47 | buck-out/
48 | \.buckd/
49 | android/app/libs
50 | android/keystores/debug.keystore
51 |
52 | # Build
53 | dist/
54 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 React Native Community
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Navigation Switch Transitioner
2 |
3 | Switch navigator for use on iOS and Android, allowing custom transitions on switching.
4 |
5 | It also includes a FadeTransition to use in your screens to get cross-fading for the switch navigator
6 |
7 | ## Installation
8 |
9 | Open a Terminal in your project's folder and run,
10 |
11 | ```sh
12 | yarn add react-navigation-switch-transitioner
13 | ```
14 |
15 | ## Usage
16 |
17 | Create your Switch Navigator with the same parameters than the react-navigation one.
18 |
19 | There are 2 options to add transitions to your Screens:
20 |
21 | ### Option 1: Using the provided HOC
22 |
23 | This library provides two HOCs: `withTransition` and `withFadeTransition` which you can use to wrap your screens. The first one allows you to inject any transition to your screen, while the second one uses the provided FadeTransition.
24 |
25 | ```js
26 | import { createSwitchNavigator, FadeTransition, withTransition, withFadeTransition } from 'react-navigation-switch-transitioner'
27 |
28 | export default createSwitchNavigator({
29 | Inbox: withTransition(FadeTransition)(InboxScreen)
30 | Drafts: withFadeTransition(DraftsScreen),
31 | }, {
32 | initialRouteName: 'Inbox',
33 | })
34 | ```
35 |
36 | ### Option 2: Wrap your screens with a transition component
37 |
38 | If you want your screens to Fade on transitioning to/from them, wrap them in the FadeTransition component and expose its navigationOptions (don't forget to pass the received props to the FadeTransition)
39 |
40 | ```js
41 | import { FadeTransition } from 'react-navigation-switch-transitioner'
42 |
43 | class DraftScreen extends React.Component {
44 | static navigationOptions = FadeTransition.navigationOptions
45 |
46 | render() {
47 | return (
48 |
49 |
56 | Drafts
57 |
58 |
59 | )
60 | }
61 | }
62 | ```
63 |
64 | ## Docs
65 |
66 | It is mostly compatible with the react-navigation `SwitchNavigator` so the best place to start while documention is created is the [React Navigation website](https://reactnavigation.org/docs/en/switch-navigator.html).
67 |
68 | ## Implementing your own Transitions
69 |
70 | If you want to implement your own transition take a look at the source code for the FadeTransition.
71 |
72 | ## Run the example app
73 |
74 | To run the example app do the following:
75 |
76 | ```sh
77 | git clone https://github.com/elyalvarado/react-navigation-switch-transitioner
78 | cd react-navigation-switch-transitioner
79 | yarn install
80 | cd examples
81 | yarn install
82 | yarn start
83 | ```
84 |
--------------------------------------------------------------------------------
/example/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "expo"
4 | ],
5 | "plugins": [
6 | ["module-resolver", {
7 | "alias": {
8 | "react-navigation-switch-transitioner": "../src"
9 | }
10 | }]
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/example/.buckconfig:
--------------------------------------------------------------------------------
1 |
2 | [android]
3 | target = Google Inc.:Google APIs:23
4 |
5 | [maven_repositories]
6 | central = https://repo1.maven.org/maven2
7 |
--------------------------------------------------------------------------------
/example/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../.eslintrc",
3 |
4 | "settings": {
5 | "import/core-modules": [ "expo", "react-navigation-stack" ]
6 | },
7 |
8 | "rules": {
9 | "react/prop-types": "off"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/example/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/example/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Expo from 'expo'
3 | import { StyleSheet, View } from 'react-native'
4 | import Fade from './src/Fade'
5 |
6 | // Comment the following two lines to stop using react-native-screens
7 | import { useScreens } from 'react-native-screens' // eslint-disable-line
8 | useScreens()
9 |
10 | const styles = StyleSheet.create({
11 | container: {
12 | flex: 1,
13 | backgroundColor: '#fff',
14 | alignItems: 'center',
15 | justifyContent: 'center',
16 | },
17 | })
18 |
19 | // eslint-disable-next-line react/prefer-stateless-function
20 | class App extends React.Component {
21 | render() {
22 | return (
23 |
24 |
25 |
26 | )
27 | }
28 | }
29 |
30 | Expo.registerRootComponent(App)
31 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | ## Run the example
2 |
3 | - Run the example locally
4 | - Clone the repository and `cd` to this directory
5 | - Run `yarn` to install the dependencies
6 | - Run `yarn start` to start the packager
7 | - Scan the QR Code with the Expo app
8 |
--------------------------------------------------------------------------------
/example/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "React Navigation Switch Transitioner Example",
4 | "description": "Demonstrates the various capabilities of react-navigation-switch-transitioner",
5 | "slug": "react-navigation-switch-transitioner-demo",
6 | "sdkVersion": "30.0.0",
7 | "version": "1.0.0",
8 | "primaryColor": "#2196f3",
9 | "packagerOpts": {
10 | "assetExts": [
11 | "ttf"
12 | ],
13 | "config": "./rn-cli.config.js",
14 | "projectRoots": ""
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stackexample",
3 | "version": "0.0.1",
4 | "private": true,
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "postinstall": "rm -rf node_modules/expo-react-native-adapter/node_modules/react && rm -rf node_modules/expo-gl/node_modules/react"
10 | },
11 | "dependencies": {
12 | "expo": "~30.0.0",
13 | "hoist-non-react-statics": "^2.5.0",
14 | "prop-types": "^15.6.0",
15 | "react": "16.3.1",
16 | "react-native": "~0.55.4",
17 | "react-native-screens": "^1.0.0-alpha.12",
18 | "react-navigation": "^2.11.1"
19 | },
20 | "devDependencies": {
21 | "babel-plugin-module-resolver": "^3.0.0",
22 | "babel-preset-expo": "^4.0.0",
23 | "glob-to-regexp": "^0.3.0"
24 | },
25 | "main": "App.js",
26 | "resolutions": {
27 | "**/react": "16.3.1",
28 | "**/prop-types": "15.6.0",
29 | "**/react-lifecycles-compat": "3.0.4",
30 | "**/hoist-non-react-statics": "2.5.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/example/rn-cli.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/no-commonjs, import/no-extraneous-dependencies */
2 |
3 | const path = require('path');
4 | const glob = require('glob-to-regexp');
5 | const blacklist = require('metro/src/blacklist');
6 | const pak = require('../package.json');
7 |
8 | const dependencies = Object.keys(pak.dependencies);
9 | const peerDependencies = Object.keys(pak.peerDependencies);
10 |
11 | module.exports = {
12 | getProjectRoots() {
13 | return [__dirname, path.resolve(__dirname, '..')];
14 | },
15 | getProvidesModuleNodeModules() {
16 | return [...dependencies, ...peerDependencies];
17 | },
18 | getBlacklistRE() {
19 | return blacklist([glob(`${path.resolve(__dirname, '..')}/node_modules/*`)]);
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/example/src/Fade.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/no-multi-comp, import/no-unresolved, react/prefer-stateless-function */
2 | import React from 'react'
3 | import {
4 | Button,
5 | Text as UnstyledText,
6 | View as UnstyledView,
7 | } from 'react-native'
8 | import {
9 | FadeTransition,
10 | createContainedSwitchNavigator,
11 | withFadeTransition,
12 | } from 'react-navigation-switch-transitioner'
13 | import { createStackNavigator } from 'react-navigation'
14 |
15 | const View = props => {
16 | const { style, ...propsNoStyle } = props
17 | return (
18 |
27 | )
28 | }
29 | const Text = props => (
30 |
31 | )
32 |
33 | // Screen without transition
34 | class HomeScreen extends React.Component {
35 | render() {
36 | const { navigation } = this.props
37 | return (
38 |
39 | Home Screen
40 |
53 | )
54 | }
55 | }
56 |
57 | // Screen with manually defined transition
58 | class AnotherScreen extends React.Component {
59 | static navigationOptions = FadeTransition.navigationOptions
60 |
61 | render() {
62 | const { navigation } = this.props
63 | return (
64 |
65 |
66 | Another Screen
67 | {
69 | navigation.navigate('HomeScreen')
70 | }}
71 | title="Go Home"
72 | />
73 | {
75 | navigation.navigate('ProfileScreen', { name: 'Jane' })
76 | }}
77 | title="Go to Jane's profile"
78 | />
79 |
80 |
81 | )
82 | }
83 | }
84 |
85 | // Screen wrapped in the router confing with a HOC that provides a transition
86 | class ProfileScreen extends React.Component {
87 | render() {
88 | const { navigation } = this.props
89 | return (
90 |
91 |
92 | {navigation.getParam('name')}
93 | 's Profile
94 |
95 | {
97 | navigation.navigate('AnotherScreen')
98 | }}
99 | title="Go to Another"
100 | />
101 | navigation.navigate('HomeScreen')}
103 | title="Go Home"
104 | />
105 |
106 | )
107 | }
108 | }
109 |
110 | const AnotherNavigator = createStackNavigator({
111 | HomeScreen,
112 | })
113 |
114 | export default createContainedSwitchNavigator(
115 | {
116 | HomeScreen: AnotherNavigator,
117 | AnotherScreen,
118 | ProfileScreen: withFadeTransition(ProfileScreen),
119 | },
120 | {
121 | initialRouteName: 'HomeScreen',
122 | },
123 | )
124 |
--------------------------------------------------------------------------------
/jest-setup.js:
--------------------------------------------------------------------------------
1 | /**
2 | * eslint-env jest
3 | */
4 |
5 | // No setup
6 |
7 | import React from 'react';
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-navigation-switch-transitioner",
3 | "version": "0.1.1",
4 | "description": "A react-navigation transitioner and switch navigator using it",
5 | "repository": "github:elyalvarado/react-navigation-switch-transitioner",
6 | "author": "Ely Alvarado",
7 | "license": "MIT",
8 | "private": false,
9 | "main": "dist/index.js",
10 | "files": [
11 | "dist/"
12 | ],
13 | "scripts": {
14 | "test": "jest",
15 | "lint": "eslint .",
16 | "format": "eslint . --fix",
17 | "precommit": "yarn lint && yarn test",
18 | "build": "babel --no-babelrc --plugins=syntax-jsx,syntax-class-properties,syntax-object-rest-spread,transform-flow-strip-types src --copy-files --out-dir dist --ignore '**/__tests__/**'",
19 | "prepare": "yarn build"
20 | },
21 | "keywords": [
22 | "react-native-component",
23 | "react-component",
24 | "react-native",
25 | "ios",
26 | "android",
27 | "switch",
28 | "navigator"
29 | ],
30 | "bugs": {
31 | "url": "https://github.com/elyalvarado/react-navigation-switch-transitioner/issues"
32 | },
33 | "homepage": "https://github.com/elyalvarado/react-navigation-switch-transitioner#readme",
34 | "dependencies": {
35 | "hoist-non-react-statics": "^3.0.1"
36 | },
37 | "devDependencies": {
38 | "babel-cli": "^6.26.0",
39 | "babel-jest": "^22.4.1",
40 | "babel-plugin-syntax-class-properties": "^6.13.0",
41 | "babel-plugin-syntax-jsx": "^6.18.0",
42 | "babel-plugin-syntax-object-rest-spread": "^6.13.0",
43 | "babel-plugin-transform-flow-strip-types": "^6.22.0",
44 | "babel-preset-react-native": "^4.0.0",
45 | "eslint": "^5.12.1",
46 | "eslint-config-airbnb": "^17.1.0",
47 | "eslint-config-prettier": "^4.0.0",
48 | "eslint-config-satya164": "^2.0.1",
49 | "eslint-plugin-import": "^2.14.0",
50 | "eslint-plugin-jsx-a11y": "^6.1.1",
51 | "eslint-plugin-react": "^7.11.1",
52 | "eslint-plugin-react-native-globals": "^0.1.0",
53 | "husky": "^1.3.1",
54 | "jest": "^22.1.3",
55 | "prettier": "^1.8.2",
56 | "react": "16.3.1",
57 | "react-native": "~0.55.4",
58 | "react-native-reanimated": "^1.0.0-alpha.8",
59 | "react-navigation": "^2.11.2",
60 | "react-test-renderer": "16.3.1"
61 | },
62 | "peerDependencies": {
63 | "react": "*",
64 | "react-native": "*",
65 | "react-native-reanimated": "^1.0.0 || ^1.0.0-alpha",
66 | "react-navigation": ">=2.0 || ^2.0.0-beta"
67 | },
68 | "jest": {
69 | "preset": "react-native",
70 | "testRegex": "/__tests__/[^/]+-test\\.js$",
71 | "setupFiles": [
72 | "/jest-setup.js"
73 | ],
74 | "coveragePathIgnorePatterns": [
75 | "jest-setup.js"
76 | ],
77 | "modulePathIgnorePatterns": [
78 | "/example/"
79 | ],
80 | "transformIgnorePatterns": [
81 | "node_modules/(?!(jest-)?react-native|react-clone-referenced-element|react-navigation-deprecated-tab-navigator|react-navigation)"
82 | ]
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 |
3 | module.exports = {
4 | /**
5 | * Navigators
6 | */
7 | get createSwitchNavigator() {
8 | return require('./navigators/createSwitchNavigator').default
9 | },
10 | get createContainedSwitchNavigator() {
11 | return require('./navigators/createContainedSwitchNavigator').default
12 | },
13 |
14 | /**
15 | * Router
16 | */
17 | get SwitchRouter() {
18 | return require('./navigators/switchRouter').default
19 | },
20 |
21 | /**
22 | * Views
23 | */
24 | get FadeTransition() {
25 | return require('./views/FadeTransition').default
26 | },
27 | get Transitioner() {
28 | return require('./views/Transitioner').default
29 | },
30 |
31 | /**
32 | * HOCs
33 | */
34 | get withTransition() {
35 | return require('./utils/withTransition').default
36 | },
37 |
38 | get withFadeTransition() {
39 | return require('./utils/withTransition').withFadeTransition
40 | },
41 | }
42 |
--------------------------------------------------------------------------------
/src/navigators/__tests__/switchRouter-test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StackRouter, NavigationActions, StackActions } from 'react-navigation'
3 | import SwitchRouter from '../switchRouter'
4 |
5 | const getExampleRouter = (config = {}) => {
6 | const PlainScreen = () =>
7 | const StackC = () =>
8 |
9 | StackC.router = StackRouter({
10 | C1: PlainScreen,
11 | C2: PlainScreen,
12 | })
13 |
14 | const router = SwitchRouter(
15 | {
16 | A: PlainScreen,
17 | B: PlainScreen,
18 | C: StackC,
19 | },
20 | {
21 | initialRouteName: 'A',
22 | ...config,
23 | },
24 | )
25 |
26 | return router
27 | }
28 |
29 | describe('switchRouter', () => {
30 | it('sets isTransitioning to true when switching screens on the same switch', () => {
31 | const router = getExampleRouter()
32 | const initialState = router.getStateForAction({
33 | type: NavigationActions.INIT,
34 | })
35 |
36 | const state2 = router.getStateForAction(
37 | { type: NavigationActions.NAVIGATE, routeName: 'B' },
38 | initialState,
39 | )
40 | expect(state2.index).toEqual(1)
41 | expect(state2.isTransitioning).toEqual(true)
42 | })
43 |
44 | it('sets isTransitioning back to false when receiving complete transition', () => {
45 | const router = getExampleRouter()
46 | const initialState = router.getStateForAction({
47 | type: NavigationActions.INIT,
48 | })
49 | const state2 = router.getStateForAction(
50 | { type: NavigationActions.NAVIGATE, routeName: 'B' },
51 | initialState,
52 | )
53 |
54 | const state3 = router.getStateForAction(
55 | { type: StackActions.COMPLETE_TRANSITION },
56 | state2,
57 | )
58 |
59 | expect(state3.isTransitioning).toEqual(false)
60 | })
61 |
62 | it("doesn't set isTransitioning when directly switching to nested screens on the navigator", () => {
63 | const router = getExampleRouter({ initialRouteName: 'C' })
64 | const initialState = router.getStateForAction({
65 | type: NavigationActions.INIT,
66 | })
67 |
68 | const state2 = router.getStateForAction(
69 | { type: NavigationActions.NAVIGATE, routeName: 'C2' },
70 | initialState,
71 | )
72 |
73 | expect(state2.isTransitioning).toEqual(false)
74 | })
75 |
76 | it("doesn't set isTransitioning when switching to nested screens on the navigator using subactions", () => {
77 | const router = getExampleRouter()
78 | const initialState = router.getStateForAction({
79 | type: NavigationActions.INIT,
80 | })
81 |
82 | const state2 = router.getStateForAction(
83 | {
84 | type: NavigationActions.NAVIGATE,
85 | routeName: 'C',
86 | action: { type: NavigationActions.NAVIGATE, routeName: 'C1' },
87 | },
88 | initialState,
89 | )
90 |
91 | expect(state2.isTransitioning).toEqual(false)
92 | })
93 |
94 | it("doesn't makes any changes when switching to the same screen", () => {
95 | const router = getExampleRouter()
96 | const initialState = router.getStateForAction({
97 | type: NavigationActions.INIT,
98 | })
99 |
100 | const state2 = router.getStateForAction(
101 | { type: NavigationActions.NAVIGATE, routeName: 'B' },
102 | initialState,
103 | )
104 |
105 | const state3 = router.getStateForAction(
106 | { type: NavigationActions.NAVIGATE, routeName: 'B' },
107 | state2,
108 | )
109 |
110 | expect(state3).toEqual(null)
111 | })
112 | })
113 |
--------------------------------------------------------------------------------
/src/navigators/createContainedSwitchNavigator.js:
--------------------------------------------------------------------------------
1 | import { createNavigationContainer } from 'react-navigation'
2 | import createSwitchNavigator from './createSwitchNavigator'
3 |
4 | export default (routeConfigs, config = {}) => {
5 | const navigator = createSwitchNavigator(routeConfigs, config)
6 | return createNavigationContainer(navigator)
7 | }
8 |
--------------------------------------------------------------------------------
/src/navigators/createSwitchNavigator.js:
--------------------------------------------------------------------------------
1 | import { createNavigator } from 'react-navigation'
2 | import Transitioner from '../views/Transitioner'
3 | import SwitchRouter from './switchRouter'
4 |
5 | export default (routeConfigMap, switchConfig = {}) => {
6 | const router = SwitchRouter(routeConfigMap, switchConfig)
7 | const Navigator = createNavigator(Transitioner, router, switchConfig)
8 | return Navigator
9 | }
10 |
--------------------------------------------------------------------------------
/src/navigators/switchRouter.js:
--------------------------------------------------------------------------------
1 | import { SwitchRouter, NavigationActions, StackActions } from 'react-navigation'
2 |
3 | export default (routeConfigs, config = {}) => {
4 | const switchRouter = SwitchRouter(routeConfigs, config)
5 | const defaultGetStateForAction = switchRouter.getStateForAction
6 |
7 | switchRouter.getStateForAction = (action, state) => {
8 | const newState = defaultGetStateForAction(action, state)
9 |
10 | const routeKeys = state && state.routes && state.routes.map(s => s.key)
11 |
12 | if (
13 | action.type === NavigationActions.NAVIGATE &&
14 | newState && // There is a state change
15 | routeKeys && // Routes are defined
16 | routeKeys.includes(action.routeName) && // Is one of my routes
17 | !action.action // And there is no sub-action
18 | ) {
19 | return {
20 | ...newState,
21 | isTransitioning:
22 | state.index !== newState.index
23 | ? action.immediate !== true
24 | : state.isTransitioning,
25 | }
26 | }
27 |
28 | if (
29 | action.type === StackActions.COMPLETE_TRANSITION &&
30 | state.isTransitioning
31 | ) {
32 | return {
33 | ...newState,
34 | isTransitioning: false,
35 | }
36 | }
37 | return newState
38 | }
39 |
40 | return switchRouter
41 | }
42 |
--------------------------------------------------------------------------------
/src/utils/withTransition.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import hoistNonReactStatic from 'hoist-non-react-statics'
3 | import FadeTransition from '../views/FadeTransition'
4 |
5 | const withTransition = Transition => Screen => {
6 | class WithTransition extends React.Component {
7 | static navigationOptions = Transition.navigationOptions
8 |
9 | render() {
10 | const {
11 | transition,
12 | transitions,
13 | transitionRouteKey,
14 | transitioningFromState,
15 | transitioningToState,
16 | style,
17 | ...nonTransitionProps
18 | } = this.props
19 | return (
20 |
21 |
22 |
23 | )
24 | }
25 | }
26 |
27 | hoistNonReactStatic(WithTransition, Screen)
28 |
29 | return WithTransition
30 | }
31 |
32 | export const withFadeTransition = withTransition(FadeTransition)
33 | export default withTransition
34 |
--------------------------------------------------------------------------------
/src/views/FadeTransition.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | import React from 'react'
3 | import Animated, { Easing } from 'react-native-reanimated'
4 |
5 | const { Value, timing, interpolate } = Animated
6 |
7 | export default class FadeTransition extends React.Component {
8 | static navigationOptions = {
9 | createTransition: transition => ({
10 | ...transition,
11 | progress: new Value(0),
12 | }),
13 | runTransition: transition =>
14 | new Promise(resolve => {
15 | timing(transition.progress, {
16 | toValue: 1,
17 | duration: 1000,
18 | easing: Easing.inOut(Easing.cubic),
19 | }).start(resolve)
20 | }),
21 | }
22 |
23 | render() {
24 | const {
25 | transition,
26 | navigation,
27 | transitioningFromState: fromState,
28 | transitioningToState: toState,
29 | } = this.props
30 |
31 | const myKey = navigation.state.key
32 | let opacity = 1
33 |
34 | if (transition && fromState) {
35 | const { progress } = transition
36 |
37 | const fromOpacity =
38 | fromState.routes[fromState.index].key === myKey ? 1 : 0
39 |
40 | const toOpacity = toState.routes[toState.index].key === myKey ? 1 : 0
41 |
42 | opacity = interpolate(progress, {
43 | inputRange: [0, 1],
44 | outputRange: [fromOpacity, toOpacity],
45 | })
46 | }
47 |
48 | const transitionStyle = [{ flex: 1, opacity }]
49 | const style = this.props.style
50 | ? [...this.props.style, ...transitionStyle]
51 | : [...transitionStyle]
52 |
53 | return {this.props.children}
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/views/Transitioner.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { StyleSheet } from 'react-native'
3 | import Animated from 'react-native-reanimated'
4 | import { StackActions, NavigationProvider } from 'react-navigation'
5 |
6 | const getKey = navState => navState.routes[navState.index].key
7 |
8 | const defaultCreateTransition = transition => ({ ...transition })
9 |
10 | const defaultRunTransition = () => {}
11 |
12 | const getStateForNavChange = (props, state) => {
13 | // by this point we know the nav state has changed and it is safe to provide a new state. static
14 | // getDerivedStateFromProps will never interrupt a transition (when there is state.isTransitioning),
15 | // and _runTransition runs this after the previous transition is complete.
16 | const { navigation } = props
17 | const nextNavState = navigation.state
18 |
19 | // Transitions are requested by setting nav state.isTransitioning to true.
20 | // If false, we set the state immediately without transition
21 | if (!nextNavState.isTransitioning) {
22 | return {
23 | isTransitioning: false,
24 | transitions: {}, // state.transitions,
25 | transitioningFromState: null,
26 | transitioningFromDescriptors: null,
27 | navState: nextNavState,
28 | descriptors: props.descriptors,
29 | }
30 | }
31 |
32 | const toKey = getKey(nextNavState)
33 | const descriptor =
34 | props.descriptors[toKey] ||
35 | state.descriptors[toKey] ||
36 | state.transitioningFromDescriptors[toKey]
37 | const { options } = descriptor
38 | const createTransition = options.createTransition || defaultCreateTransition
39 | const transition = createTransition({ navigation: props.navigation })
40 |
41 | // Get and create the previous screen transition
42 | const fromKey = getKey(state.navState)
43 | const fromDescriptor =
44 | props.descriptors[fromKey] ||
45 | state.descriptors[fromKey] ||
46 | state.transitioningFromDescriptors[fromKey]
47 | const { options: fromOptions } = fromDescriptor
48 | const createFromTransition =
49 | fromOptions.createTransition || defaultCreateTransition
50 | const fromTransition = createFromTransition({ navigation: props.navigation })
51 |
52 | return {
53 | isTransitioning: true,
54 | transitions: {
55 | [toKey]: transition,
56 | [fromKey]: fromTransition,
57 | },
58 | transitioningFromState: state.navState,
59 | transitioningFromDescriptors: state.descriptors,
60 | navState: nextNavState,
61 | descriptors: props.descriptors,
62 | }
63 | }
64 |
65 | export default class Transitioner extends React.Component {
66 | state = {
67 | // if this true, there is a transition in progress:
68 | isTransitioning: false,
69 | // an object of transitions by route key
70 | transitions: {},
71 | // this will be the previous nav state and descriptors, when there is a transition in progress
72 | transitioningFromState: null,
73 | transitioningFromDescriptors: {},
74 | // this is the current navigation state and descriptors:
75 | navState: this.props.navigation.state,
76 | descriptors: this.props.descriptors,
77 | }
78 |
79 | static getDerivedStateFromProps = (props, state) => {
80 | // Transition only happens when nav state changes
81 | if (props.navigation.state === state.navState) {
82 | return state
83 | }
84 | // Never interrupt a transition in progress.
85 | if (state.isTransitioning) {
86 | return state
87 | }
88 |
89 | return getStateForNavChange(props, state)
90 | }
91 |
92 | componentDidUpdate(lastProps, lastState) {
93 | const { isTransitioning, transitioningFromState } = this.state
94 | if (
95 | // If we are transitioning
96 | isTransitioning &&
97 | // And this is a new transition,
98 | lastState.transitioningFromState !== transitioningFromState
99 | ) {
100 | this._startTransition().then(
101 | () => {},
102 | e => {
103 | console.error('Error running transition:', e)
104 | },
105 | )
106 | }
107 | }
108 |
109 | completeTransition = () => {
110 | const { navigation } = this.props
111 | if (navigation.state.isTransitioning) {
112 | navigation.dispatch(
113 | StackActions.completeTransition({
114 | key: navigation.state.key,
115 | }),
116 | )
117 | }
118 | }
119 |
120 | async _startTransition() {
121 | // Put state in function scope, so we are confident that we refer to the exact same state later for getStateForNavChange.
122 | // Even though our state shouldn't change during the animation.
123 | const { state } = this
124 | const {
125 | transitions,
126 | transitioningFromState,
127 | transitioningFromDescriptors,
128 | navState,
129 | descriptors,
130 | } = state
131 |
132 | // get the current screen transition
133 | const toKey = getKey(navState)
134 | const descriptor = descriptors[toKey] || transitioningFromDescriptors[toKey]
135 | const { runTransition } = descriptor.options
136 | const run = runTransition || defaultRunTransition
137 | const transition = transitions[toKey]
138 |
139 | // get the previous screen transition
140 | const fromKey = getKey(transitioningFromState)
141 | const fromDescriptor =
142 | descriptor[fromKey] || transitioningFromDescriptors[fromKey]
143 | const { runTransition: runFromTransition } = fromDescriptor.options
144 | const runFrom = runFromTransition || defaultRunTransition
145 | const fromTransition = transitions[fromKey]
146 |
147 | // Run animation, this might take some time..
148 | // await oldRun(oldTransition, transitioningFromState, navState);
149 | // await run(transition, transitioningFromState, navState);
150 | await Promise.all([
151 | runFrom(fromTransition, transitioningFromState, navState),
152 | run(transition, transitioningFromState, navState),
153 | ])
154 |
155 | // after async animator, this.props may have changed. re-check it now:
156 | // eslint-disable-next-line react/destructuring-assignment
157 | if (navState === this.props.navigation.state) {
158 | // Navigation state is currently the exact state we were transitioning to. Set final state and we're done
159 | this.setState({
160 | isTransitioning: false,
161 | transitioningFromState: null,
162 | transitioningFromDescriptors: {},
163 | // navState and descriptors remain unchanged at this point.
164 | })
165 | } else {
166 | // Navigation state prop has changed during the transtion! Schedule another transition
167 | this.setState(getStateForNavChange(this.props, state))
168 | }
169 |
170 | this.completeTransition()
171 | }
172 |
173 | render() {
174 | const {
175 | transitions,
176 | isTransitioning,
177 | transitioningFromState,
178 | transitioningFromDescriptors,
179 | navState,
180 | descriptors,
181 | } = this.state
182 | const { screenProps, navigation } = this.props
183 | const backScreenStyles = {} // FIX THIS:
184 |
185 | const activeKey = getKey(navState)
186 | const incomingDescriptor =
187 | descriptors[activeKey] || transitioningFromDescriptors[activeKey]
188 | const incomingOptions = incomingDescriptor && incomingDescriptor.options
189 | const incomingHasManagedTransition =
190 | incomingOptions &&
191 | incomingOptions.runTransition &&
192 | incomingOptions.createTransition
193 | const incomingTransition = transitions[activeKey]
194 | const IncomingScreen = incomingDescriptor.getComponent()
195 | const transitioningToState = isTransitioning ? navigation.state : null
196 |
197 | const oldKey = transitioningFromState && getKey(transitioningFromState)
198 | const outgoingDescriptor = oldKey && transitioningFromDescriptors[oldKey]
199 | const outgoingOptions = outgoingDescriptor && outgoingDescriptor.options
200 | const outgoingHasManagedTransition =
201 | outgoingOptions &&
202 | outgoingOptions.runTransition &&
203 | outgoingOptions.createTransition
204 | const outgoingTransition = oldKey && transitions[oldKey]
205 | const OutgoingScreen =
206 | outgoingDescriptor && outgoingDescriptor.getComponent()
207 |
208 | // make sure we're filling the screen with our AnimatedView and Screens
209 | const style = [{ flex: 1, ...StyleSheet.absoluteFillObject }]
210 |
211 | const passdownProps = {
212 | isTransitioning,
213 | transitions,
214 | transitioningFromState,
215 | transitioningToState,
216 | style,
217 | }
218 |
219 | // We shouldn't pass down the managed transition props to other screens
220 | // because it might cause propsValidation errors. This was happening with
221 | // the navigators
222 | const outgoingProps = outgoingHasManagedTransition
223 | ? {
224 | ...passdownProps,
225 | transition: outgoingTransition,
226 | screenProps,
227 | }
228 | : {
229 | screenProps,
230 | }
231 |
232 | const incomingProps = incomingHasManagedTransition
233 | ? {
234 | ...passdownProps,
235 | transition: incomingTransition,
236 | screenProps,
237 | }
238 | : {
239 | screenProps,
240 | }
241 |
242 | const screens = [
243 | (
244 |
249 | )
250 | ]
251 |
252 | if(OutgoingScreen) {
253 | const outgoingWithProps = (
254 |
259 | )
260 | // The outgoing screen is on top if the incoming screen has no managed
261 | // transition but the outgoing has one, else is below the incoming one.
262 | // This allows showing a transition on transitioning to screens with
263 | // nested navigators coming from a simple screen.
264 | if(!incomingHasManagedTransition && outgoingHasManagedTransition) {
265 | screens.push(outgoingWithProps)
266 | } else {
267 | screens.unshift(outgoingWithProps)
268 | }
269 | }
270 |
271 | return (
272 |
276 |
277 | { [ ...screens ] }
278 |
279 |
280 | )
281 | }
282 | }
283 |
--------------------------------------------------------------------------------