├── .gitignore
├── .npmignore
├── .prettierrc
├── HISTORY.md
├── LICENSE
├── README.md
├── babel.config.js
├── example
├── babel.config.js
├── index.html
├── package.json
├── src
│ ├── NavBar.js
│ ├── Screens
│ │ ├── Docs
│ │ │ ├── AnimatedRoute
│ │ │ │ ├── Code.js
│ │ │ │ ├── Demo.js
│ │ │ │ ├── Overview.js
│ │ │ │ ├── example.js
│ │ │ │ └── index.js
│ │ │ ├── AnimatedSwitch
│ │ │ │ ├── Code.js
│ │ │ │ ├── Demo.js
│ │ │ │ ├── Overview.js
│ │ │ │ ├── Props.js
│ │ │ │ ├── example.css
│ │ │ │ ├── example.js
│ │ │ │ └── index.js
│ │ │ ├── GettingStarted.js
│ │ │ ├── index.js
│ │ │ └── shared
│ │ │ │ ├── Browser
│ │ │ │ ├── Input.js
│ │ │ │ ├── TopBar.js
│ │ │ │ ├── Viewport.js
│ │ │ │ ├── Wrapper.js
│ │ │ │ └── index.js
│ │ │ │ ├── ComponentPage.js
│ │ │ │ ├── Footer.js
│ │ │ │ ├── Page.js
│ │ │ │ └── PageNav.js
│ │ ├── Home.js
│ │ └── Screen.js
│ ├── globals.css
│ ├── grayscale-prism-theme.css
│ └── index.js
└── webpack.config.js
├── index.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.swp
3 |
4 | example/bundle.*
5 |
6 | lib
7 | es
8 |
9 | node_modules
10 |
11 | package-lock.json
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /.*
2 |
3 | /example/
4 |
5 | /webpack.*
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "bracketSpacing": true,
4 | "jsxBracketSameLine": false,
5 | "printWidth": 80,
6 | "requirePragma": false,
7 | "semi": true,
8 | "singleQuote": true,
9 | "tabWidth": 2,
10 | "tabs": false,
11 | "trailingComma": "all"
12 | }
13 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | ### 0.0.6 (08/31/2016)
2 | * Added `react` to webpack `externals`
3 |
4 | ### 0.0.5 (04/08/2016)
5 | * Added `runOnMount` to `RouteTransition`
6 | * Added react to devDependencies
7 |
8 | ### 0.0.4 (02/11/2016)
9 | * Initial release
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Richard Maisano
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 Router Transition
2 |
3 | Painless transitions for React Router, powered by React Motion. [Example site](http://maisano.github.io/react-router-transition/).
4 |
5 | ### Requirements
6 |
7 | To use the latest version of this package (`2.x`), you'll need to use a version
8 | of React compatible with hooks, as well as version `5.x` of `react-router-dom`.
9 |
10 | ### Installation
11 |
12 | `npm install --save react-router-transition react-router-dom`
13 |
14 | ### Example Usage
15 | ```jsx
16 | import { BrowserRouter as Router, Route } from 'react-router-dom';
17 | import { AnimatedSwitch } from 'react-router-transition';
18 |
19 | export default () => (
20 |
21 |
27 |
28 |
29 |
30 |
31 |
32 | )
33 | ```
34 |
35 | ```css
36 | .switch-wrapper {
37 | position: relative;
38 | }
39 |
40 | .switch-wrapper > div {
41 | position: absolute;
42 | }
43 | ```
44 |
45 | ### Docs
46 |
47 | - [AnimatedSwitch](http://maisano.github.io/react-router-transition/animated-switch)
48 | - [AnimatedRoute](http://maisano.github.io/react-router-transition/animated-route)
49 |
50 | ### Limitations
51 |
52 | This library has some obvious limitations, the most marked of which are:
53 |
54 | - no staggering or sequencing of animations
55 | - no durations or timing functions
56 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-react',
5 | {
6 | useBuiltIns: true,
7 | },
8 | ],
9 | [
10 | '@babel/preset-env',
11 | {
12 | modules: false,
13 | },
14 | ],
15 | ],
16 | };
17 |
--------------------------------------------------------------------------------
/example/babel.config.js:
--------------------------------------------------------------------------------
1 | const glamor = require('glamor/babel');
2 |
3 | module.exports = {
4 | presets: [
5 | [
6 | '@babel/preset-react',
7 | {
8 | useBuiltIns: true,
9 | },
10 | ],
11 | [
12 | '@babel/preset-env',
13 | {
14 | modules: false,
15 | },
16 | ],
17 | ],
18 | plugins: [
19 | '@babel/plugin-proposal-class-properties',
20 | [
21 | '@babel/plugin-transform-react-jsx',
22 | {
23 | pragma: 'Glamor.createElement',
24 | },
25 | ],
26 | // Glamor doesn't ship a Babel 7 compliant plugin.
27 | () => glamor,
28 | ],
29 | };
30 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React Router Transition
6 |
7 |
8 |
9 |
10 |
11 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-router-transition-example",
3 | "version": "1.0.0",
4 | "description": "An example showcasing some react-router-transition usage",
5 | "scripts": {
6 | "build": "NODE_ENV=production webpack -p",
7 | "start": "webpack-dev-server --history-api-fallback"
8 | },
9 | "author": "maisano",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "@babel/core": "^7.6.2",
13 | "@babel/plugin-proposal-class-properties": "^7.5.5",
14 | "@babel/preset-env": "^7.6.2",
15 | "@babel/preset-react": "^7.0.0",
16 | "babel-loader": "^8.0.6",
17 | "classnames": "^2.2.6",
18 | "css-loader": "^3.2.0",
19 | "glamor": "^2.20.40",
20 | "prismjs": "^1.17.1",
21 | "prismjs-loader": "0.0.4",
22 | "style-loader": "^1.0.0",
23 | "webpack": "^4.41.0",
24 | "webpack-cli": "^3.3.9",
25 | "webpack-dev-server": "^3.8.2"
26 | },
27 | "dependencies": {}
28 | }
29 |
--------------------------------------------------------------------------------
/example/src/NavBar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, NavLink } from 'react-router-dom';
3 | import { css } from 'glamor';
4 |
5 | const rule = css`
6 | display: flex;
7 | padding-top: 2rem;
8 | margin-top: -2rem;
9 | height: 5rem;
10 | line-height: 5rem;
11 | background-color: #202020;
12 |
13 | & a {
14 | color: #eee;
15 | height: 5rem;
16 | line-height: 5rem;
17 | padding: 0 2rem;
18 | flex-grow: 1;
19 | text-align: center;
20 | }
21 |
22 | & a:hover, & a.active {
23 | background-color: #2a2a2a;
24 | color: #fff;
25 | }
26 |
27 | @media(max-width: 600px) {
28 | justify-content: space-around;
29 | overflow: hidden;
30 |
31 | & a {
32 | padding: 0;
33 | font-size: 1.2rem;
34 | }
35 |
36 | & a.github {
37 | display: none;
38 | }
39 | }
40 | `;
41 |
42 | const NavBar = () => (
43 |
44 | ←
45 | Getting started
46 | Animated Switch
47 | Animated Route
48 |
52 | GitHub
53 |
54 |
55 | );
56 |
57 | export default NavBar;
58 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/AnimatedRoute/Code.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Footer from '../shared/Footer';
4 |
5 | import AnimatedRouteJS from '!!prismjs-loader?lang=jsx!./example';
6 |
7 | const ExampleCode = () => (
8 |
9 |
Example Code
10 |
11 |
AnimatedRouteDemo.js
12 |
16 |
17 |
18 |
19 | );
20 |
21 | export default ExampleCode;
22 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/AnimatedRoute/Demo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { css } from 'glamor';
4 |
5 | import { AnimatedRoute } from 'react-router-transition';
6 |
7 | import Browser from '../shared/Browser';
8 | import Footer from '../shared/Footer';
9 |
10 | const bodyRule = css`
11 | display: flex;
12 | height: 100%;
13 | width: 100%;
14 | justify-content: center;
15 | align-items: center;
16 | `;
17 |
18 | const wrapperRule = css`
19 | position: absolute;
20 | top: 0;
21 | bottom: 0;
22 | `;
23 |
24 | const routeRule = css`
25 | position: relative;
26 | height: 100%;
27 | width: 200px;
28 |
29 | & > div {
30 | position: absolute;
31 | width: 100%;
32 | height: 100%;
33 | }
34 | `;
35 |
36 | const sidebarRule = css`
37 | height: 100%;
38 | padding: 2rem;
39 | box-sizing: border-box;
40 | background-color: #fafafa;
41 | `;
42 |
43 | const sidebarTransition = {
44 | atEnter: {
45 | offset: -100,
46 | },
47 | atLeave: {
48 | offset: -100,
49 | },
50 | atActive: {
51 | offset: 0,
52 | },
53 | };
54 |
55 | function mapStyles(styles) {
56 | return {
57 | transform: `translateX(${styles.offset}%)`,
58 | };
59 | }
60 |
61 | const Sidebar = () => (
62 |
63 |
64 |
Hi, hello, I'm a sidebar.
65 |
I'm not entirely certain that this component is useful, but it's kinda fun.
66 |
Close me.
67 |
68 |
69 | );
70 |
71 | const AnimatedRouteDemo = () => (
72 |
73 |
Demo
74 |
75 |
78 |
87 |
88 |
89 |
90 | );
91 |
92 | export default AnimatedRouteDemo;
93 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/AnimatedRoute/Overview.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { css } from 'glamor';
3 |
4 | import Footer from '../shared/Footer';
5 |
6 | const Element = ({ name }) => (
7 | <{name} />
8 | );
9 |
10 | const Overview = () => (
11 |
12 |
Overview
13 |
A , but with mounting & unmounting transitions.
14 |
Interruptible
15 |
Transitions on an are interruptible . This means that if an animation is currently in motion and its match is toggled, it will redirect itself to its proper state mid-transition.
16 |
Nestable
17 |
You can put instances inside one another! You can even make them recursively nest and match–it's very likely not useful, but it sure is interesting.
18 |
19 |
20 | );
21 |
22 | export default Overview;
23 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/AnimatedRoute/example.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { AnimatedRoute } from 'react-router-transition';
4 |
5 | import Wrapper from './Wrapper';
6 | import Sidebar from './Sidebar';
7 |
8 | export default () => (
9 |
10 | Show sidebar
11 | ({
18 | transform: `translateX(${styles.offset}%)`,
19 | })}
20 | />
21 |
22 | );
23 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/AnimatedRoute/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import ComponentPage from '../shared/ComponentPage';
4 |
5 | import Overview from './Overview';
6 | import Props from '../AnimatedSwitch/Props'; // lolol
7 | import Demo from './Demo';
8 | import Code from './Code';
9 |
10 | const sections = [{
11 | title: 'Overview',
12 | path: '/animated-route',
13 | exact: true,
14 | component: Overview,
15 | }, {
16 | title: 'Props',
17 | path: '/animated-route/props',
18 | component: Props,
19 | }, {
20 | title: 'Demo',
21 | path: '/animated-route/demo',
22 | component: Demo,
23 | }, {
24 | title: 'Code',
25 | path: '/animated-route/code',
26 | component: Code,
27 | }];
28 |
29 | export default () => (
30 |
34 | );
35 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/AnimatedSwitch/Code.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Footer from '../shared/Footer';
4 |
5 | import AnimatedSwitchJS from '!!prismjs-loader?lang=jsx!./example';
6 | import AnimatedSwitchCSS from '!!prismjs-loader?lang=css!./example.css';
7 |
8 | const ExampleCode = () => (
9 |
10 |
Example Code
11 |
12 |
AnimatedSwitchDemo.js
13 |
17 |
18 |
19 |
AnimatedSwitchDemo.css
20 |
24 |
25 |
26 |
27 | );
28 |
29 | export default ExampleCode;
30 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/AnimatedSwitch/Demo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink, Route } from 'react-router-dom';
3 | import { css } from 'glamor';
4 |
5 | import { AnimatedSwitch, spring } from 'react-router-transition';
6 |
7 | import Browser from '../shared/Browser';
8 | import Footer from '../shared/Footer';
9 |
10 | const rule = css`
11 | position: relative;
12 | height: calc("100% - 40px");
13 |
14 | & > div {
15 | position: absolute;
16 | width: 100%;
17 | height: 100%;
18 | }
19 | `;
20 |
21 | const navRule = css`
22 | display: flex;
23 | height: 4rem;
24 | line-height: 4rem;
25 | justify-content: center;
26 | background-color: #fafafa;
27 |
28 | & a {
29 | flex-grow: 1;
30 | text-align: center;
31 | color: #303030;
32 | }
33 |
34 | & a:first-child {
35 | border-right: 1px solid #fff;
36 | }
37 | `;
38 |
39 | const activeRule = css`
40 | background-color: #f5f5f5;
41 | `;
42 |
43 | const demoPageRule = css`
44 | height: 100%;
45 | width: 100%;
46 | display: flex;
47 | justify-content: center;
48 | align-items: center;
49 | font-size: 400px;
50 | `;
51 |
52 | function bounce(val) {
53 | return spring(val, {
54 | stiffness: 330,
55 | damping: 22,
56 | });
57 | }
58 |
59 | const demoConfig = {
60 | atEnter: {
61 | opacity: 0,
62 | scale: 1.2,
63 | },
64 | atLeave: {
65 | opacity: bounce(0),
66 | scale: bounce(0.8),
67 | },
68 | atActive: {
69 | opacity: bounce(1),
70 | scale: bounce(1),
71 | },
72 | };
73 |
74 | function mapStyles(styles) {
75 | return {
76 | opacity: styles.opacity,
77 | transform: `scale(${styles.scale})`,
78 | };
79 | }
80 |
81 | const DemoPage = ({ children }) => (
82 |
83 | {children}
84 |
85 | );
86 |
87 | const A = () => (
88 | A
89 | );
90 |
91 | const B = () => (
92 | B
93 | );
94 |
95 | const C = () => (
96 | C
97 | );
98 |
99 | const Demo = () => (
100 |
101 |
Demo
102 |
103 |
104 | A
105 | B
106 | C
107 |
108 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | );
121 |
122 | export default Demo;
123 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/AnimatedSwitch/Overview.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { css } from 'glamor';
3 |
4 | import Footer from '../shared/Footer';
5 |
6 | const Overview = () => (
7 |
8 |
Overview
9 |
A <Switch />
, but with transitions when the child route changes. Allows for animating sibling routes with a mounting and unmounting transition.
10 |
Uninterruptible
11 |
Transitions within an Animated Switch are not interruptible . This is by design, as we will mount each newly matched child route as a separate component with a unique key.
12 |
Nestable
13 |
Just like <Switch />
, this component is meant to be used anywhere you are rendering exclusive matches. This means nesting transitions is simple, providing your layout affords usage.
14 |
15 |
16 | );
17 |
18 | export default Overview;
19 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/AnimatedSwitch/Props.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { css } from 'glamor';
3 |
4 | import Footer from '../shared/Footer';
5 |
6 | const propTypesRule = css`
7 | font-size: 1.6rem;
8 | border-bottom: 1px solid #eee;
9 | padding-bottom: 1rem;
10 | margin: 3rem 0 1rem;
11 | `;
12 |
13 | const Props = () => (
14 |
15 |
Props
16 |
17 | atEnter
, atLeave
, atActive
18 |
19 |
Objects with numerical values, expressing the interpolatable states a component will have when mounting, unmounting and mounted, respectively. Note that spring
objects are valid only for atActive
and atLeave
.
20 |
didLeave
21 |
An optional function that will be called when animation is done.
22 |
mapStyles
23 |
An optional function for transforming values that don't map 1:1 with styles (e.g. translateX
or other values of the transform
style property).
24 |
runOnMount
25 |
A boolean flag to signal whether or not to apply a transition to the child component while mounting the parent.
26 |
wrapperComponent
27 |
The element type ('div'
, 'span'
, etc.) to wrap the transitioning routes with. Use false
to transition child components themselves, though this requires consuming a style
prop that gets injected into your component .
28 |
className
29 |
A class name to apply to the root node of the transition.
30 |
31 |
32 | );
33 |
34 | export default Props;
35 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/AnimatedSwitch/example.css:
--------------------------------------------------------------------------------
1 | .route-wrapper {
2 | position: relative;
3 | }
4 |
5 | .route-wrapper > div {
6 | position: absolute;
7 | }
8 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/AnimatedSwitch/example.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route } from 'react-router-dom';
3 | import { spring, AnimatedSwitch } from 'react-router-transition';
4 |
5 | import A from './A';
6 | import B from './B';
7 | import C from './C';
8 |
9 | // we need to map the `scale` prop we define below
10 | // to the transform style property
11 | function mapStyles(styles) {
12 | return {
13 | opacity: styles.opacity,
14 | transform: `scale(${styles.scale})`,
15 | };
16 | }
17 |
18 | // wrap the `spring` helper to use a bouncy config
19 | function bounce(val) {
20 | return spring(val, {
21 | stiffness: 330,
22 | damping: 22,
23 | });
24 | }
25 |
26 | // child matches will...
27 | const bounceTransition = {
28 | // start in a transparent, upscaled state
29 | atEnter: {
30 | opacity: 0,
31 | scale: 1.2,
32 | },
33 | // leave in a transparent, downscaled state
34 | atLeave: {
35 | opacity: bounce(0),
36 | scale: bounce(0.8),
37 | },
38 | // and rest at an opaque, normally-scaled state
39 | atActive: {
40 | opacity: bounce(1),
41 | scale: bounce(1),
42 | },
43 | };
44 |
45 | export default () => (
46 |
53 |
54 |
55 |
56 |
57 | );
58 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/AnimatedSwitch/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import ComponentPage from '../shared/ComponentPage';
4 |
5 | import Overview from './Overview';
6 | import Props from './Props';
7 | import Demo from './Demo';
8 | import Code from './Code';
9 |
10 | const sections = [{
11 | title: 'Overview',
12 | path: '/animated-switch',
13 | exact: true,
14 | component: Overview,
15 | }, {
16 | title: 'Props',
17 | path: '/animated-switch/props',
18 | component: Props,
19 | }, {
20 | title: 'Demo',
21 | path: '/animated-switch/demo',
22 | component: Demo,
23 | }, {
24 | title: 'Code',
25 | path: '/animated-switch/code',
26 | component: Code,
27 | }];
28 |
29 | export default () => (
30 |
34 | );
35 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/GettingStarted.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | import Page from './shared/Page';
5 | import Footer from './shared/Footer';
6 |
7 | const Element = ({ name }) => (
8 | <{name} />
9 | );
10 |
11 | export default () => (
12 |
13 |
14 |
Getting started
15 |
Installation
16 |
npm install --save react-router-transition
17 |
What's included
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | spring
34 |
35 |
36 |
Design
37 |
React Router Transition tries to be as simple as possible. While some configuration is required, the library exposes two components that act as thin layers on top of components from React Router DOM. Where these are not flexible enough, the package also exports a lower-level wrapper around React Motion's .
38 |
The package also exports React Motion's spring
helper function for specifying the spring configuration for the animation. To learn more about this helper, check the React Motion README .
39 |
Limitations
40 |
This library has some obvious limitations, the most marked of which are:
41 |
42 | no staggering or sequencing of animations
43 | no durations or timing functions
44 |
45 |
If you're looking for one of the above features, I would recommend using Animated and Transition Group .
46 |
47 |
48 |
49 | );
50 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route } from 'react-router-dom';
3 | import { css } from 'glamor';
4 |
5 | import { AnimatedSwitch, spring } from 'react-router-transition';
6 |
7 | import Screen from '../Screen';
8 |
9 | import GettingStartedPage from './GettingStarted';
10 | import AnimatedSwitchPage from './AnimatedSwitch';
11 | import AnimatedRoutePage from './AnimatedRoute';
12 |
13 | const rule = css`
14 | padding-top: 5rem;
15 | box-sizing: border-box;
16 | background-color: #fff;
17 | color: #333;
18 | `;
19 |
20 | const switchRule = css`
21 | position: relative;
22 | height: calc("100vh - 50px");
23 | width: 100vw;
24 | background-color: #fff;
25 |
26 | & > div {
27 | position: absolute;
28 | width: 100%;
29 | height: 100%;
30 | overflow-y: auto;
31 | -webkit-overflow-scrolling: touch;
32 | }
33 | `;
34 |
35 | function mapStyles(styles) {
36 | return {
37 | opacity: styles.opacity,
38 | transform: `translateX(${styles.offset}px)`,
39 | };
40 | }
41 |
42 | function glide(val) {
43 | return spring(val, {
44 | stiffness: 174,
45 | damping: 19,
46 | });
47 | }
48 |
49 | const pageTransitions = {
50 | atEnter: {
51 | offset: 200,
52 | opacity: 0,
53 | },
54 | atLeave: {
55 | offset: glide(-100),
56 | opacity: glide(0),
57 | },
58 | atActive: {
59 | offset: glide(0),
60 | opacity: glide(1),
61 | },
62 | };
63 |
64 | export default () => (
65 |
66 |
67 |
72 |
73 |
74 |
75 |
76 |
77 |
78 | );
79 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/shared/Browser/Input.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { css } from 'glamor';
4 |
5 | const inputRule = css`
6 | flex-grow: 1;
7 | margin-left: 8px;
8 | padding: 5px 7px;
9 | border-radius: 3px;
10 | border: 1px solid transparent;
11 | font-size: 12px;
12 | line-height: 1.5em;
13 |
14 | &:focus {
15 | outline: none;
16 | border-color: rgb(89, 163, 240);
17 | }
18 | `;
19 |
20 | class LocationInput extends Component {
21 | static propTypes = {
22 | pathname: PropTypes.string.isRequired,
23 | push: PropTypes.func.isRequired,
24 | };
25 |
26 | state = {
27 | value: this.props.pathname,
28 | };
29 |
30 | componentWillReceiveProps(nextProps) {
31 | if (this.props.pathname !== nextProps.pathname) {
32 | this.setState({
33 | value: nextProps.pathname,
34 | });
35 | }
36 | }
37 |
38 | selectText = () => {
39 | this.node.setSelectionRange(1, this.node.value.length);
40 | }
41 |
42 | handleChange = (e) => {
43 | this.setState({
44 | value: e.target.value,
45 | });
46 | }
47 |
48 | handleKeyDown = (e) => {
49 | if (e.key === 'Escape') {
50 | this.setState({
51 | value: this.props.pathname,
52 | }, this.selectText);
53 | }
54 | }
55 |
56 | handleKeyPress = (e) => {
57 | if (e.key === 'Enter') {
58 | this.props.push(e.target.value);
59 | }
60 | }
61 |
62 | handleBlur = () => {
63 | this.setState({
64 | value: this.props.pathname,
65 | });
66 | }
67 |
68 | render() {
69 | return (
70 | { this.node = n; }}
80 | />
81 | );
82 | }
83 | }
84 |
85 | export default LocationInput;
86 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/shared/Browser/TopBar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { css } from 'glamor';
4 |
5 | import { useHistory, useLocation } from 'react-router-dom';
6 |
7 | import LocationInput from './Input';
8 |
9 | const topBarRule = css`
10 | display: flex;
11 | padding: 7px;
12 | border-bottom: 2px solid rgb(236, 236, 236);
13 | background-color: rgb(246, 246, 246);
14 | `;
15 |
16 | const buttonRule = css`
17 | border: none;
18 | background: none;
19 | padding: 3px 7px;
20 | font-size: 1.6rem;
21 | margin: 0 2px;
22 | border-radius: 3px;
23 | color: rgb(111, 111, 111);
24 | transition: background-color 150ms ease-in-out;
25 |
26 | &:focus {
27 | outline: none;
28 | }
29 |
30 | &:hover {
31 | background-color: rgb(231, 231, 231);
32 | }
33 |
34 | &[disabled] {
35 | color: rgb(219, 219, 219);
36 | }
37 |
38 | &[disabled]:hover {
39 | background-color: transparent;
40 | }
41 | `;
42 |
43 | const TopBar = (props) => {
44 | const history = useHistory();
45 | const location = useLocation();
46 |
47 | return (
48 |
49 |
54 | ←
55 |
56 |
61 | →
62 |
63 |
67 |
68 | )
69 | };
70 |
71 | export default TopBar;
72 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/shared/Browser/Viewport.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { css } from 'glamor';
3 |
4 | const viewportRule = css`
5 | overflow: hidden;
6 | height: 480px;
7 | position: relative;
8 | `;
9 |
10 | const viewportBodyRule = css`
11 | height: 100%;
12 | overflow: auto;
13 | `;
14 |
15 | const Viewport = ({ children }) => (
16 |
17 |
18 | {children}
19 |
20 |
21 | );
22 |
23 | export default Viewport;
24 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/shared/Browser/Wrapper.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { css } from 'glamor';
3 |
4 | const wrapperRule = css`
5 | max-width: 100%;
6 | border: 2px solid rgb(236, 236, 236);
7 | box-sizing: border-box;
8 | border-radius: 4px;
9 | margin: 0 auto 3rem;
10 | overflow: hidden;
11 | `;
12 |
13 | const Wrapper = ({ children }) => (
14 |
15 | {children}
16 |
17 | );
18 |
19 | export default Wrapper;
20 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/shared/Browser/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { MemoryRouter as Router } from 'react-router-dom';
3 |
4 | import BrowserWrapper from './Wrapper';
5 | import BrowserTopBar from './TopBar';
6 | import BrowserViewport from './Viewport';
7 |
8 | const Browser = ({ children, defaultPathname }) => (
9 |
10 |
11 |
12 |
13 | {children}
14 |
15 |
16 |
17 | );
18 |
19 | Browser.defaultProps = {
20 | defaultPathname: '/',
21 | };
22 |
23 | export default Browser;
24 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/shared/ComponentPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route } from 'react-router-dom';
3 | import { css } from 'glamor';
4 |
5 | import Page from './Page';
6 | import PageNav from './PageNav';
7 |
8 | import { AnimatedSwitch, spring } from 'react-router-transition';
9 |
10 | const switchRule = css`
11 | position: relative;
12 |
13 | & > div {
14 | position: absolute;
15 | width: 100%;
16 | padding-bottom: 3rem;
17 | }
18 | `;
19 |
20 | function zoom(val) {
21 | return spring(val, {
22 | stiffness: 135,
23 | damping: 15,
24 | });
25 | }
26 |
27 | const switchConfig = {
28 | atEnter: {
29 | opacity: 0,
30 | offset: -50,
31 | },
32 | atLeave: {
33 | opacity: 0,
34 | offset: zoom(50),
35 | },
36 | atActive: {
37 | opacity: 1,
38 | offset: zoom(0),
39 | },
40 | };
41 |
42 | function mapStyles(styles) {
43 | return {
44 | opacity: styles.opacity,
45 | transform: `translateY(${styles.offset}px)`,
46 | };
47 | }
48 |
49 | const ComponentPage = ({ sections, title }) => (
50 |
51 |
55 |
60 | {sections.map(({ title, ...rest }) => (
61 |
62 | ))}
63 |
64 |
65 | );
66 |
67 | export default ComponentPage;
68 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/shared/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { css } from 'glamor';
3 |
4 | const rule = css`
5 | border-top: 1px solid #f1f1f1;
6 | margin-top: 2rem;
7 | padding-top: 2rem;
8 | color: #909090;
9 | font-size: 1rem;
10 |
11 | & a {
12 | color: #505050;
13 | }
14 |
15 | & a:hover {
16 | background-color: #eee;
17 | }
18 | `;
19 |
20 | const Footer = () => (
21 |
22 | );
23 |
24 | export default Footer;
25 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/shared/Page.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { css } from 'glamor';
3 |
4 | const rule = css`
5 | padding: 5rem 3rem;
6 |
7 | @media(max-width: 600px) {
8 | padding: 2rem 3rem 3rem;
9 | }
10 |
11 | & > * {
12 | width: 730px;
13 | margin: 0 auto;
14 | max-width: 100%;
15 | }
16 |
17 | & pre[class*="language-"] {
18 | padding: 2rem;
19 | box-sizing: border-box;
20 | }
21 |
22 | & a {
23 | color: rgb(40, 100, 220);
24 | }
25 |
26 | & a:hover,
27 | & a:visited {
28 | color: rgb(30, 80, 180);
29 | }
30 | `;
31 |
32 | const Page = ({ children }) => (
33 |
34 | {children}
35 |
36 | );
37 |
38 | export default Page;
39 |
--------------------------------------------------------------------------------
/example/src/Screens/Docs/shared/PageNav.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink } from 'react-router-dom';
3 | import { css } from 'glamor';
4 |
5 | const rule = css`
6 | padding-bottom: 3rem;
7 | `;
8 |
9 | const linkRule = css`
10 | background-color: white;
11 | margin: 0 1rem 0 0;
12 | padding: 1rem;
13 | text-transform: uppercase;
14 | font-size: 1.2rem;
15 | border-radius: 2px;
16 | transition: background-color 100ms ease-in-out;
17 |
18 | &:hover {
19 | background-color: #f7f7f7;
20 | }
21 |
22 | &.active {
23 | background-color: #f1f1f1;
24 | }
25 | `;
26 |
27 | const PageNav = ({ sections }) => (
28 |
29 | {sections.map(({ exact, title, path }) => (
30 |
36 | {title}
37 |
38 | ))}
39 |
40 | );
41 |
42 | export default PageNav;
43 |
--------------------------------------------------------------------------------
/example/src/Screens/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { css } from 'glamor';
4 |
5 | import Screen from './Screen';
6 |
7 | const rule = css`
8 | padding: 0 5rem;
9 | width: 960px;
10 | max-width: 100%;
11 | box-sizing: border-box;
12 | margin: 0 auto;
13 |
14 | @media(max-width: 600px) {
15 | padding: 0 3rem 3rem;
16 | }
17 | `;
18 |
19 | const titleRule = css`
20 | font-size: 1.8rem;
21 | font-weight: 300;
22 | margin-top: 10vh;
23 | color: #fff;
24 |
25 | @media(max-width: 600px) {
26 | margin-top: 6rem;
27 | }
28 | `;
29 |
30 | const bodyRule = css`
31 | margin-top: 50vh;
32 |
33 | & span {
34 | font-size: 3.8rem;
35 | }
36 |
37 | @media(max-width: 600px) {
38 | margin-top: 14rem;
39 | }
40 |
41 | @media(min-width: 800px) {
42 | & span {
43 | display: block;
44 | }
45 | }
46 | `;
47 |
48 | const buttonsRule = css`
49 | margin-top: 10vh;
50 | `;
51 |
52 | const buttonRule = css`
53 | display: inline-block;
54 | font-size: 1.6rem;
55 | padding: 1rem 2.5rem;
56 | margin: 0.5rem 1.2rem 0.5rem 0;
57 | background-color: #202020;
58 | border: 1px solid #eee;
59 | border-radius: 1px;
60 | color: #fff;
61 |
62 | &:visited {
63 | color: #fff;
64 | }
65 |
66 | &:hover {
67 | color: #202020;
68 | border-color: #202020;
69 | background-color: #fff;
70 | }
71 | `;
72 |
73 | const primaryButtonRule = css`
74 | color: #202020;
75 | border-color: #202020;
76 | background-color: #eee
77 |
78 | &:visited {
79 | color: #202020;
80 | }
81 | `;
82 |
83 | export default () => (
84 |
85 |
86 |
React Router Transition
87 |
88 | Painless transitions built for react-router,
89 | powered by react-motion
90 |
91 |
102 |
103 |
104 | );
105 |
--------------------------------------------------------------------------------
/example/src/Screens/Screen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { css } from 'glamor';
3 |
4 | const rule = css`
5 | height: 100vh;
6 | width: 100vw;
7 | overflow-y: auto;
8 | `;
9 |
10 | const Screen = ({ children }) => (
11 |
12 | {children}
13 |
14 | );
15 |
16 | export default Screen;
17 |
--------------------------------------------------------------------------------
/example/src/globals.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | html, body {
7 | height: 100%;
8 | width: 100%;
9 | }
10 |
11 | html {
12 | font-size: 62.5%;
13 | }
14 |
15 | body {
16 | font-family: 'Roboto', sans-serif;
17 | font-size: 14px;
18 | font-size: 1.4rem;
19 | letter-spacing: 0.033rem;
20 | background-color: #202020;
21 | color: #eee;
22 | }
23 |
24 | h1 {
25 | font-size: 3rem;
26 | margin: 2.3rem 0;
27 | color: #505050;
28 | }
29 |
30 | h2 {
31 | font-size: 2.2rem;
32 | margin: 2rem 0;
33 | color: #303030;
34 | }
35 |
36 | h3 {
37 | margin: 1.5rem 0;
38 | }
39 |
40 | a {
41 | text-decoration: none;
42 | }
43 |
44 | p {
45 | font-size: 1.6rem;
46 | line-height: 1.7em;
47 | margin-bottom: 1.6rem;
48 | }
49 |
50 | ul {
51 | margin-bottom: 1.6rem;
52 | }
53 |
54 | li {
55 | font-size: 1.6rem;
56 | line-height: 1.7em;
57 | }
58 |
59 | code {
60 | background-color: #f1f1f1;
61 | border-radius: 3px;
62 | padding: 0.3rem 0.7rem;
63 | vertical-align: top;
64 | }
65 |
66 | pre {
67 | font-size: 1.3rem;
68 | }
69 |
--------------------------------------------------------------------------------
/example/src/grayscale-prism-theme.css:
--------------------------------------------------------------------------------
1 | /**
2 | * A theme based on @simurai's duotone-dark-sea-syntax
3 | */
4 | code[class*="language-"],
5 | pre[class*="language-"] {
6 | color: #888;
7 | background: #fafafa;
8 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
9 | text-align: left;
10 | white-space: pre;
11 | word-spacing: normal;
12 | word-break: normal;
13 | word-wrap: normal;
14 | line-height: 1.5;
15 |
16 | -moz-tab-size: 2;
17 | -o-tab-size: 2;
18 | tab-size: 2;
19 |
20 | -webkit-hyphens: none;
21 | -moz-hyphens: none;
22 | -ms-hyphens: none;
23 | hyphens: none;
24 | }
25 |
26 | /* Code blocks */
27 | pre[class*="language-"] {
28 | margin: .5em 0;
29 | overflow: auto;
30 | }
31 |
32 | :not(pre) > code[class*="language-"],
33 | pre[class*="language-"] {
34 | }
35 |
36 | /* Inline code */
37 | :not(pre) > code[class*="language-"] {
38 | padding: .1em;
39 | border-radius: .3em;
40 | white-space: normal;
41 | }
42 |
43 | .token.comment,
44 | .token.block-comment,
45 | .token.prolog,
46 | .token.doctype,
47 | .token.cdata {
48 | color: #bbb;
49 | }
50 |
51 | .token.punctuation {
52 | color: #bbb;
53 | }
54 |
55 | .token.tag,
56 | .token.namespace,
57 | .token.deleted {
58 | color: #555;
59 | }
60 |
61 | .token.attr-name {
62 | color: #777;
63 | }
64 |
65 | .token.function-name {
66 | color: #888;
67 | }
68 |
69 | .token.boolean,
70 | .token.number,
71 | .token.function {
72 | color: #666;
73 | }
74 |
75 | .token.property,
76 | .token.class-name,
77 | .token.constant,
78 | .token.symbol {
79 | color: #555;
80 | }
81 |
82 | .token.selector,
83 | .token.important,
84 | .token.atrule,
85 | .token.keyword,
86 | .token.builtin {
87 | color: #666;
88 | }
89 |
90 | .token.string,
91 | .token.char,
92 | .token.attr-value,
93 | .token.regex,
94 | .token.variable {
95 | color: #555;
96 | }
97 |
98 | .token.operator,
99 | .token.entity,
100 | .token.url {
101 | color: #777;
102 | }
103 |
104 | .token.important,
105 | .token.bold {
106 | font-weight: bold;
107 | }
108 | .token.italic {
109 | font-style: italic;
110 | }
111 |
112 | .token.entity {
113 | cursor: help;
114 | }
115 |
116 | .token.inserted {
117 | color: #ccc;
118 | }
119 |
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { BrowserRouter as Router, Route } from 'react-router-dom';
4 | import { css } from 'glamor';
5 |
6 | import NavBar from './NavBar';
7 |
8 | import LandingScreen from './Screens/Home';
9 | import DocumentationScreen from './Screens/Docs';
10 |
11 | import { AnimatedSwitch, AnimatedRoute, spring } from 'react-router-transition';
12 |
13 | import './globals.css';
14 | import './grayscale-prism-theme.css';
15 |
16 | // silly workaround for gh-pages
17 | const basename = process.env.NODE_ENV === 'production'
18 | ? '/react-router-transition'
19 | : null;
20 |
21 | const switchRule = css`
22 | position: relative;
23 |
24 | & > div {
25 | position: absolute;
26 | }
27 | `;
28 |
29 | const routeRule = css`
30 | position: relative;
31 |
32 | & > div {
33 | position: absolute;
34 | width: 100%;
35 | }
36 | `;
37 |
38 | function glide(val) {
39 | return spring(val, {
40 | stiffness: 174,
41 | damping: 24,
42 | });
43 | }
44 |
45 | function slide(val) {
46 | return spring(val, {
47 | stiffness: 125,
48 | damping: 16,
49 | });
50 | }
51 |
52 | const pageTransitions = {
53 | atEnter: {
54 | offset: 100,
55 | },
56 | atLeave: {
57 | offset: glide(-100),
58 | },
59 | atActive: {
60 | offset: glide(0),
61 | },
62 | };
63 |
64 | const topBarTransitions = {
65 | atEnter: {
66 | offset: -100,
67 | },
68 | atLeave: {
69 | offset: slide(-150),
70 | },
71 | atActive: {
72 | offset: slide(0),
73 | },
74 | };
75 |
76 | const App = () => (
77 |
78 | (
79 |
80 |
({
85 | transform: `translateX(${styles.offset}%)`,
86 | })}
87 | >
88 |
89 |
90 |
91 |
({
97 | transform: `translateY(${styles.offset}%)`,
98 | })}
99 | />
100 |
101 | )} />
102 |
103 | );
104 |
105 | ReactDOM.render(
106 | ,
107 | document.getElementById('root')
108 | );
109 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const webpack = require('webpack');
5 |
6 | const src = path.join(__dirname, 'src');
7 | const parentDir = path.join(__dirname, '../');
8 |
9 | module.exports = {
10 | devtool: 'sourcemap',
11 |
12 | entry: path.join(src, 'index.js'),
13 |
14 | output: {
15 | path: __dirname,
16 | filename: 'bundle.js',
17 | },
18 |
19 | module: {
20 | rules: [
21 | {
22 | test: /\.js$/,
23 | loader: 'babel-loader',
24 | include: [src, parentDir],
25 | },
26 | {
27 | test: /\.css$/,
28 | use: ['style-loader', 'css-loader'],
29 | },
30 | ],
31 | },
32 |
33 | resolve: {
34 | modules: ['node_modules'],
35 |
36 | alias: {
37 | 'react-router-transition': path.resolve(parentDir, 'index.js'),
38 | },
39 | },
40 |
41 | resolveLoader: {
42 | modules: ['node_modules'],
43 | },
44 |
45 | plugins: [
46 | new webpack.DefinePlugin({
47 | 'process.env.NODE_ENV': JSON.stringify(
48 | process.env.NODE_ENV || 'development',
49 | ),
50 | }),
51 |
52 | new webpack.ProvidePlugin({
53 | Glamor: require.resolve('glamor/react'),
54 | }),
55 | ],
56 | };
57 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import React, {
2 | cloneElement,
3 | createElement,
4 | useEffect,
5 | useRef,
6 | useState,
7 | } from 'react';
8 |
9 | import PropTypes from 'prop-types';
10 |
11 | import { Route, Switch, matchPath, useLocation } from 'react-router-dom';
12 |
13 | import TransitionMotion from 'react-motion/lib/TransitionMotion';
14 | import spring from 'react-motion/lib/spring';
15 |
16 | // Helpers
17 |
18 | function ensureSpring(styles) {
19 | const obj = {};
20 |
21 | for (var key in styles) {
22 | const value = styles[key];
23 | if (typeof value === 'number') {
24 | obj[key] = spring(value);
25 | } else {
26 | obj[key] = value;
27 | }
28 | }
29 |
30 | return obj;
31 | }
32 |
33 | function identity(v) {
34 | return v;
35 | }
36 |
37 | function noop() {}
38 |
39 | // Components
40 |
41 | function RouteTransition({
42 | children,
43 | className,
44 | atEnter,
45 | atActive,
46 | atLeave,
47 | wrapperComponent = 'div',
48 | didLeave = noop,
49 | mapStyles = identity,
50 | runOnMount = false,
51 | }) {
52 | const defaultStyles =
53 | runOnMount === false
54 | ? null
55 | : children == undefined
56 | ? []
57 | : [
58 | {
59 | key: children.key,
60 | data: children,
61 | style: atEnter,
62 | },
63 | ];
64 |
65 | const styles =
66 | children == undefined
67 | ? []
68 | : [
69 | {
70 | key: children.key,
71 | data: children,
72 | style: ensureSpring(atActive),
73 | },
74 | ];
75 |
76 | return (
77 | atEnter}
81 | willLeave={() => ensureSpring(atLeave)}
82 | didLeave={didLeave}
83 | >
84 | {(interpolatedStyles) => (
85 |
86 | {interpolatedStyles.map((config) => {
87 | const props = {
88 | style: mapStyles(config.style),
89 | key: config.key,
90 | };
91 |
92 | return wrapperComponent !== false
93 | ? createElement(wrapperComponent, props, config.data)
94 | : cloneElement(config.data, props);
95 | })}
96 |
97 | )}
98 |
99 | );
100 | }
101 |
102 | RouteTransition.propTypes = {
103 | className: PropTypes.string,
104 | wrapperComponent: PropTypes.oneOfType([
105 | PropTypes.bool,
106 | PropTypes.element,
107 | PropTypes.string,
108 | PropTypes.func,
109 | ]),
110 | atEnter: PropTypes.object.isRequired,
111 | atActive: PropTypes.object.isRequired,
112 | atLeave: PropTypes.object.isRequired,
113 | didLeave: PropTypes.func,
114 | mapStyles: PropTypes.func,
115 | runOnMount: PropTypes.bool,
116 | };
117 |
118 | // AnimatedRoute
119 |
120 | // The key-getter for RouteTransition. It's either on or off.
121 | function getKey({ pathname }, path, exact) {
122 | return matchPath(pathname, { exact, path }) ? 'match' : 'no-match';
123 | }
124 |
125 | function AnimatedRoute({
126 | render,
127 | component,
128 | path,
129 | exact,
130 | strict,
131 | sensitive,
132 | children,
133 | ...routeTransitionProps
134 | }) {
135 | const location = useLocation();
136 |
137 | return (
138 |
139 |
150 |
151 | );
152 | }
153 |
154 | // AnimatedSwitch
155 |
156 | const NO_MATCH = {
157 | key: 'no-match',
158 | };
159 |
160 | // Not every location object has a `key` property (e.g. HashHistory).
161 | function getLocationKey(location) {
162 | return typeof location.key === 'string' ? location.key : '';
163 | }
164 |
165 | // Some superfluous work, but something we need to do in order
166 | // to persist matches/allow for nesting/etc.
167 | function getMatchedRoute(children, { pathname }) {
168 | const childrenArray = React.Children.toArray(children);
169 |
170 | for (let i = 0; i < childrenArray.length; i++) {
171 | const child = childrenArray[i];
172 | const matches = matchPath(pathname, {
173 | exact: child.props.exact,
174 | path: child.props.path,
175 | });
176 |
177 | if (matches) {
178 | return child;
179 | }
180 | }
181 |
182 | return NO_MATCH;
183 | }
184 |
185 | let counter = 0;
186 |
187 | function AnimatedSwitch({ children, ...routeTransitionProps }) {
188 | const location = useLocation();
189 | const match = useRef(null);
190 | const key = useRef(null);
191 |
192 | const nextMatch = getMatchedRoute(children, location);
193 |
194 | if (match.current === null) {
195 | // Persist a reference to the most recent match
196 | match.current = nextMatch;
197 | key.current = getLocationKey(location);
198 |
199 | } else if (match.current.key !== nextMatch.key) {
200 | // Update the key given to Switch anytime the matched route changes
201 | match.current = nextMatch;
202 | key.current = getLocationKey(location) + ++counter;
203 | }
204 |
205 | return (
206 |
207 |
208 | {children}
209 |
210 |
211 | );
212 | }
213 |
214 | export { ensureSpring, spring, RouteTransition, AnimatedRoute, AnimatedSwitch };
215 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-router-transition",
3 | "version": "2.1.0",
4 | "description": "A thin layer over react-motion for animating routes in react-router.",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "build": "babel index.js --out-dir lib",
8 | "clean": "rm -rf lib",
9 | "gh-pages": "git b -D gh-pages && git co -b gh-pages && (cd example && npm run build) && cp example/{index.html,bundle.js} ./ && cp index.html 404.html && git add . && git commit -m 'gh-pages' && git push origin gh-pages --force",
10 | "prepublishOnly": "npm run clean && npm run build",
11 | "test": "echo \"Error: no test specified\" && exit 1"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/maisano/react-router-transition.git"
16 | },
17 | "keywords": [
18 | "react",
19 | "react-router",
20 | "react-motion",
21 | "animation",
22 | "transition"
23 | ],
24 | "author": "maisano",
25 | "license": "MIT",
26 | "bugs": {
27 | "url": "https://github.com/maisano/react-router-transition/issues"
28 | },
29 | "homepage": "https://github.com/maisano/react-router-transition#readme",
30 | "peerDependencies": {
31 | "react": "^16.8.0",
32 | "react-dom": "^16.8.0",
33 | "react-router-dom": "^5.0.0"
34 | },
35 | "dependencies": {
36 | "prop-types": "^15.7.2",
37 | "react-motion": "^0.5.2"
38 | },
39 | "devDependencies": {
40 | "@babel/cli": "^7.6.2",
41 | "@babel/core": "^7.6.2",
42 | "@babel/preset-env": "^7.6.2",
43 | "@babel/preset-react": "^7.0.0",
44 | "prettier": "^1.18.2",
45 | "react": "^16.10.2",
46 | "react-dom": "^16.10.2",
47 | "react-router-dom": "^5.1.2"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------