├── src ├── utils │ ├── secondsMs.js │ ├── shouldNavigate.js │ ├── getPagesPromises.js │ └── triggerTransition.js ├── gatsby-ssr.js ├── hooks │ ├── index.js │ ├── useTransitionState.js │ └── useTriggerTransition.js ├── gatsby-browser.js ├── style.css ├── context │ ├── createTransitionContext.js │ └── InternalProvider.js ├── wrap-page.js ├── functions │ ├── onExit.js │ └── onEnter.js ├── index.js ├── wrap-root.js ├── gatsby-node.js ├── AniLink │ ├── MorphTo.js │ ├── index.js │ ├── Fade.js │ ├── Cover.js │ ├── Swipe.js │ └── PaintDrip.js └── components │ ├── delayTransitionRender.js │ ├── Layout.js │ ├── TransitionPortal.js │ ├── TransitionObserver.js │ ├── TransitionRenderer.js │ ├── TransitionLink.js │ └── TransitionHandler.js ├── example ├── .prettierrc ├── .eslintrc.json ├── src │ ├── images │ │ └── gatsby-icon.png │ ├── layout │ │ └── index.js │ ├── pages │ │ ├── 404.js │ │ ├── react-spring │ │ │ ├── a.js │ │ │ └── b.js │ │ ├── react-pose │ │ │ ├── pose-1.js │ │ │ └── pose-2.js │ │ ├── morph │ │ │ ├── b.js │ │ │ └── index.js │ │ ├── programmatic │ │ │ ├── a.js │ │ │ └── b.js │ │ ├── index.js │ │ └── page-2.js │ └── components │ │ ├── DisplayState.js │ │ ├── programmatic.js │ │ ├── react-spring-animation.js │ │ ├── header.js │ │ ├── GithubLink.js │ │ ├── layout.js │ │ ├── Poses │ │ └── ExamplePose.js │ │ └── layout.css ├── .gitignore ├── gatsby-browser.js ├── gatsby-config.js ├── LICENSE ├── package.json └── README.md ├── images ├── gatsby-plugin-transition-link.png └── gatsby-plugin-transition-link-timeline.png ├── .gitignore ├── netlify.toml ├── babel.config.js ├── .babelrc ├── .npmignore ├── package.json ├── readme.md ├── todo └── CHANGELOG.md /src/utils/secondsMs.js: -------------------------------------------------------------------------------- 1 | const getMs = seconds => seconds * 1000 2 | 3 | export { getMs } 4 | -------------------------------------------------------------------------------- /example/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /example/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "TL__GATSBY_LAYOUT_COMPONENT_PATH": false 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | exports.wrapPageElement = require(`./wrap-page`) 2 | exports.wrapRootElement = require(`./wrap-root`) 3 | -------------------------------------------------------------------------------- /example/src/images/gatsby-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TylerBarnes/gatsby-plugin-transition-link/HEAD/example/src/images/gatsby-icon.png -------------------------------------------------------------------------------- /src/hooks/index.js: -------------------------------------------------------------------------------- 1 | export { useTransitionState } from './useTransitionState' 2 | export { useTriggerTransition } from './useTriggerTransition' 3 | -------------------------------------------------------------------------------- /images/gatsby-plugin-transition-link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TylerBarnes/gatsby-plugin-transition-link/HEAD/images/gatsby-plugin-transition-link.png -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Project dependencies 2 | .cache 3 | node_modules 4 | yarn-error.log 5 | package-lock.json 6 | 7 | # Build directory 8 | /public 9 | .DS_Store 10 | -------------------------------------------------------------------------------- /images/gatsby-plugin-transition-link-timeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TylerBarnes/gatsby-plugin-transition-link/HEAD/images/gatsby-plugin-transition-link-timeline.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | lib/ 3 | node_modules/ 4 | yarn-error.log 5 | package-lock.json 6 | /*.js 7 | !babel.config.js 8 | src/.eslintrc.json 9 | src/package.json 10 | src/readme.md 11 | -------------------------------------------------------------------------------- /src/gatsby-browser.js: -------------------------------------------------------------------------------- 1 | exports.wrapPageElement = require(`./wrap-page`) 2 | exports.wrapRootElement = require(`./wrap-root`) 3 | 4 | exports.shouldUpdateScroll = () => !window.__tl_inTransition 5 | -------------------------------------------------------------------------------- /example/src/layout/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default ({ children }) => ( 4 | <> 5 | {children} 6 |

persistent footer!

7 | 8 | ) 9 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "example/" 3 | publish = "example/public/" 4 | command = "npm run build" 5 | [build.environment] 6 | YARN_VERSION = "1.3.2" 7 | YARN_FLAGS = "--no-ignore-optional" 8 | -------------------------------------------------------------------------------- /example/gatsby-browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Browser APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/browser-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | 'babel-preset-gatsby-package', 5 | { 6 | browser: true, 7 | }, 8 | ], 9 | ], 10 | plugins: ['@babel/plugin-proposal-class-properties'], 11 | } 12 | -------------------------------------------------------------------------------- /src/hooks/useTransitionState.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { publicContext } from '../context/createTransitionContext' 3 | 4 | const useTransitionState = () => useContext(publicContext) 5 | 6 | export { useTransitionState } 7 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | .tl-edges { 2 | max-width: 100%; 3 | overflow-x: hidden; 4 | } 5 | 6 | .tl-wrapper { 7 | width: 100%; 8 | float: left; 9 | position: relative; 10 | } 11 | 12 | .tl-wrapper + .tl-wrapper { 13 | margin-left: -100%; 14 | margin-right: 0; 15 | } 16 | -------------------------------------------------------------------------------- /example/src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Layout from '../components/layout' 3 | 4 | const NotFoundPage = () => ( 5 | 6 |

NOT FOUND

7 |

You just hit a route that doesn't exist... the sadness.

8 |
9 | ) 10 | 11 | export default NotFoundPage 12 | -------------------------------------------------------------------------------- /example/src/pages/react-spring/a.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Layout from '../../components/layout' 3 | import { MySpring, SpringLink } from '../../components/react-spring-animation' 4 | 5 | const A = () => ( 6 | 7 |

React spring

8 | 9 | to page b! 10 |
11 | ) 12 | 13 | export default A 14 | -------------------------------------------------------------------------------- /example/src/pages/react-spring/b.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Layout from '../../components/layout' 3 | import { MySpring, SpringLink } from '../../components/react-spring-animation' 4 | 5 | const B = () => ( 6 | 7 |

React spring

8 | 9 | to page a! 10 |
11 | ) 12 | 13 | export default B 14 | -------------------------------------------------------------------------------- /src/context/createTransitionContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | 3 | const Context = createContext() 4 | const { Provider, Consumer } = Context 5 | 6 | const publicContext = createContext() 7 | const { Provider: PublicProvider, Consumer: PublicConsumer } = publicContext 8 | 9 | export { 10 | Provider, 11 | Consumer, 12 | Context, 13 | PublicProvider, 14 | PublicConsumer, 15 | publicContext, 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/shouldNavigate.js: -------------------------------------------------------------------------------- 1 | /* 2 | * adapted from @reach/router implementation: 3 | * defintion: https://github.com/reach/router/blob/master/src/index.js#L542-L545 4 | * usage: https://github.com/reach/router/blob/master/src/index.js#L391-L397 5 | */ 6 | 7 | const shouldNavigate = event => 8 | !event.defaultPrevented && 9 | event.button === 0 && 10 | !(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) 11 | 12 | export { shouldNavigate } 13 | -------------------------------------------------------------------------------- /src/utils/getPagesPromises.js: -------------------------------------------------------------------------------- 1 | export default function getPagesPromises() { 2 | let exitResolve 3 | const exitPromise = new Promise(resolve => { 4 | exitResolve = resolve 5 | }) 6 | 7 | let entryResolve 8 | const entryPromise = new Promise(resolve => { 9 | entryResolve = resolve 10 | }) 11 | 12 | return { 13 | triggerResolve: { 14 | entry: entryResolve, 15 | exit: exitResolve, 16 | }, 17 | pages: { 18 | exit: exitPromise, 19 | entry: entryPromise, 20 | }, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/wrap-page.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const TransitionHandler = require('./components/TransitionHandler').default 3 | const Layout = require('./components/Layout').LayoutComponent 4 | 5 | // eslint-disable-next-line react/prop-types,react/display-name 6 | module.exports = ({ element, props }, pluginOptions) => { 7 | return ( 8 | 9 | 10 | {element} 11 | 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /example/src/pages/react-pose/pose-1.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Layout from '../../components/layout' 3 | import Example from '../../components/Poses/ExamplePose' 4 | 5 | class Index extends Component { 6 | constructor(props) { 7 | super(props) 8 | } 9 | 10 | render() { 11 | return ( 12 | 13 |

React pose

14 | 15 |
16 | ) 17 | } 18 | } 19 | 20 | export default Index 21 | -------------------------------------------------------------------------------- /example/src/pages/react-pose/pose-2.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Layout from '../../components/layout' 3 | import Example from '../../components/Poses/ExamplePose' 4 | 5 | class Index extends Component { 6 | constructor(props) { 7 | super(props) 8 | } 9 | 10 | render() { 11 | return ( 12 | 13 |

React pose

14 | 15 |
16 | ) 17 | } 18 | } 19 | 20 | export default Index 21 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["babel-preset-gatsby-package"]], 3 | "plugins": [ 4 | [ 5 | "babel-plugin-root-import", 6 | { 7 | "rootPathSuffix": "./src/", 8 | "rootPathPrefix": "~/" 9 | } 10 | ], 11 | [ 12 | "@babel/plugin-proposal-private-methods", 13 | { 14 | "loose": true 15 | } 16 | ], 17 | [ 18 | "@babel/plugin-proposal-class-properties", 19 | { 20 | "loose": true 21 | } 22 | ], 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /src/functions/onExit.js: -------------------------------------------------------------------------------- 1 | const onExit = ({ 2 | node, 3 | inTransition, 4 | exitTrigger, 5 | entryProps, 6 | exitProps, 7 | triggerResolve, 8 | e, 9 | }) => { 10 | if (!inTransition) return 11 | 12 | const { trigger: removed, ...exitPropsTrimmed } = exitProps 13 | 14 | triggerResolve.exit({ 15 | ...exitPropsTrimmed, 16 | node, 17 | }) 18 | 19 | return ( 20 | exitTrigger && 21 | typeof exitTrigger === 'function' && 22 | exitTrigger({ 23 | entry: entryProps, 24 | exit: exitProps, 25 | node, 26 | e, 27 | }) 28 | ) 29 | } 30 | 31 | export { onExit } 32 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { TransitionLink } from './components/TransitionLink' 2 | import TransitionHandler from './components/TransitionHandler' 3 | import { PublicConsumer as TransitionState } from './context/createTransitionContext' 4 | import TransitionPortal from './components/TransitionPortal' 5 | import TransitionObserver from './components/TransitionObserver' 6 | import { useTriggerTransition } from './hooks' 7 | 8 | export { 9 | TransitionHandler, 10 | TransitionState, 11 | TransitionPortal, 12 | TransitionObserver, 13 | useTriggerTransition, 14 | } 15 | export default TransitionLink 16 | -------------------------------------------------------------------------------- /src/wrap-root.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const { navigate } = require('gatsby') 3 | 4 | const InternalProvider = require('./context/InternalProvider').default 5 | 6 | module.exports = ({ element }) => { 7 | if (typeof window !== `undefined`) { 8 | window.addEventListener('popstate', function(event) { 9 | // prevent the back button during transitions as it breaks pages 10 | if (window.__tl_inTransition) { 11 | window.__tl_back_button_pressed = true 12 | navigate(window.__tl_desiredPathname) 13 | } 14 | }) 15 | } 16 | 17 | return {element} 18 | } 19 | -------------------------------------------------------------------------------- /example/src/pages/morph/b.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import AniLink from 'gatsby-plugin-transition-link/AniLink' 4 | import Layout from '../../components/layout' 5 | 6 | export default () => ( 7 | 8 | 16 |
24 | 25 | 26 | ) 27 | -------------------------------------------------------------------------------- /example/src/pages/morph/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AniLink from 'gatsby-plugin-transition-link/AniLink' 3 | import Layout from '../../components/layout' 4 | 5 | const MorphPage = () => ( 6 | 7 | 15 |
23 | 24 | 25 | ) 26 | 27 | export default MorphPage 28 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | *.un~ 29 | yarn.lock 30 | src 31 | flow-typed 32 | coverage 33 | decls 34 | examples 35 | example -------------------------------------------------------------------------------- /src/gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require(`path`) 2 | 3 | let didRunAlready = false 4 | let absoluteComponentPath 5 | 6 | exports.onPreInit = ({ store }, { layout: component }) => { 7 | if (!component) { 8 | component = false 9 | } 10 | 11 | if (didRunAlready) { 12 | throw new Error( 13 | `You can only have single instance of gatsby-plugin-transition-link in your gatsby-config.js` 14 | ) 15 | } 16 | 17 | didRunAlready = true 18 | absoluteComponentPath = component 19 | } 20 | 21 | exports.onCreateWebpackConfig = ({ actions, plugins }) => { 22 | actions.setWebpackConfig({ 23 | plugins: [ 24 | plugins.define({ 25 | TL__GATSBY_LAYOUT_COMPONENT_PATH: JSON.stringify( 26 | absoluteComponentPath 27 | ), 28 | }), 29 | ], 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /example/src/components/DisplayState.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { TransitionState } from 'gatsby-plugin-transition-link' 3 | 4 | function print_r(o) { 5 | if (typeof window === `undefined`) return 6 | 7 | return JSON.stringify(o, null, '\t') 8 | .replace(/\n/g, '
') 9 | .replace(/\t/g, '   ') 10 | } 11 | 12 | function DisplayState() { 13 | return ( 14 | 15 | {context => 16 | context ? ( 17 |
18 |

Current transition state

19 | 20 |
21 |           
22 | ) : null 23 | } 24 |
25 | ) 26 | } 27 | 28 | export default DisplayState 29 | -------------------------------------------------------------------------------- /example/src/components/programmatic.js: -------------------------------------------------------------------------------- 1 | import { TimelineMax } from 'gsap' 2 | 3 | export const fade = ({ exit: { length }, node, direction }) => { 4 | const duration = direction === 'out' ? length + length / 4 : length 5 | const opacity = direction === 'in' ? 1 : 0 6 | const scrollTop = 7 | (document.scrollingElement && document.scrollingElement.scrollTop) || 8 | document.body.scrollTop || 9 | (typeof window !== `undefined` && window.pageYOffset) 10 | 11 | const holdPosition = 12 | direction === 'out' 13 | ? { 14 | overflowY: 'hidden', 15 | height: '100vh', 16 | scrollTop: scrollTop, 17 | } 18 | : {} 19 | 20 | return new TimelineMax() 21 | .set(node, holdPosition) 22 | .fromTo(node, duration, { opacity: !opacity }, { opacity: opacity }) 23 | } 24 | -------------------------------------------------------------------------------- /example/src/pages/programmatic/a.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useTriggerTransition } from 'gatsby-plugin-transition-link' 3 | import Layout from '../../components/layout' 4 | import { fade } from '../../components/programmatic' 5 | 6 | const A = () => { 7 | const triggerTransition = useTriggerTransition({ 8 | to: '/programmatic/b', 9 | exit: { 10 | length: 1, 11 | trigger: ({ exit, node }) => fade({ exit, node, direction: 'out' }), 12 | }, 13 | entry: { 14 | length: 0.5, 15 | delay: 0.5, 16 | trigger: ({ exit, node }) => fade({ exit, node, direction: 'in' }), 17 | }, 18 | }) 19 | return ( 20 | 21 |

Hello programmatic people

22 | 23 | Go to page B programmatically onClick 24 | 25 |
26 | ) 27 | } 28 | 29 | export default A 30 | -------------------------------------------------------------------------------- /src/AniLink/MorphTo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TransitionLink from '../' 3 | import gsap from 'gsap' 4 | 5 | const MorphTo = ({ children, to, duration, morph }) => ( 6 | { 15 | const exit = await pages.exit 16 | const entry = await pages.entry 17 | 18 | const morphFromEl = exit.node.querySelector(morph.from) 19 | const morphToEl = entry.node.querySelector(morph.to) 20 | 21 | const finalMeasurements = { 22 | height: morphToEl.offsetHeight, 23 | width: morphToEl.offsetWidth, 24 | } 25 | 26 | gsap.to(morphFromEl, { 27 | width: finalMeasurements.width, 28 | height: finalMeasurements.height, 29 | duration, 30 | }) 31 | }}> 32 | {children} 33 | 34 | ) 35 | 36 | export default MorphTo 37 | -------------------------------------------------------------------------------- /src/AniLink/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Cover from './Cover' 3 | import Fade from './Fade' 4 | import PaintDrip from './PaintDrip' 5 | import SwipeOver from './Swipe' 6 | import TransitionLink from '../' 7 | import MorphTo from './MorphTo' 8 | 9 | export default function DefaultTransition(allProps) { 10 | const { children, ...props } = allProps 11 | return ( 12 | <> 13 | {props.cover && {children}} 14 | {props.fade && {children}} 15 | {props.paintDrip && {children}} 16 | {props.swipe && {children}} 17 | {!!props.morph && {children}} 18 | 19 | {!props.cover && 20 | !props.fade && 21 | !props.paintDrip && 22 | !props.swipe && 23 | !props.morph && ( 24 | {children} 25 | )} 26 | 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /example/gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: 'Transition Link examples', 4 | }, 5 | plugins: [ 6 | 'gatsby-plugin-ngrok-tunneling', 7 | 'gatsby-plugin-react-helmet', 8 | { 9 | resolve: `gatsby-plugin-manifest`, 10 | options: { 11 | name: 'gatsby-starter-default', 12 | short_name: 'Transition Link example', 13 | start_url: '/', 14 | background_color: '#663399', 15 | theme_color: '#663399', 16 | display: 'minimal-ui', 17 | icon: 'src/images/gatsby-icon.png', // This path is relative to the root of the site. 18 | }, 19 | }, 20 | // 'gatsby-plugin-offline', 21 | 'gatsby-plugin-remove-serviceworker', 22 | { 23 | resolve: 'gatsby-plugin-transition-link', 24 | options: { 25 | layout: require.resolve(`./src/layout`), 26 | }, 27 | }, 28 | // 'gatsby-plugin-transition-link', 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /src/components/delayTransitionRender.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { setTimeout, clearTimeout } from 'requestanimationframe-timer' 3 | 4 | export default function delayTransitionRender(WrappedComponent) { 5 | class DelayedTransitionWrapper extends Component { 6 | constructor(props) { 7 | super(props) 8 | 9 | this.state = { 10 | // if there is a delay, set shouldRender to false 11 | // then in componentdid mount shouldRender becomes true 12 | // after the delay. 13 | shouldRender: !!!this.props.delay, 14 | } 15 | } 16 | 17 | componentDidMount() { 18 | this.renderTimeout = setTimeout( 19 | () => this.setState({ shouldRender: true }), 20 | this.props.delay 21 | ) 22 | } 23 | 24 | componentWillUnmount() { 25 | clearTimeout(this.renderTimeout) 26 | } 27 | 28 | render() { 29 | return this.state.shouldRender || typeof window === `undefined` ? ( 30 | 31 | ) : null 32 | } 33 | } 34 | 35 | return DelayedTransitionWrapper 36 | } 37 | -------------------------------------------------------------------------------- /example/src/components/react-spring-animation.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Spring } from 'react-spring' 3 | import TransitionLink, { TransitionState } from 'gatsby-plugin-transition-link' 4 | 5 | const MySpring = ({ text }) => ( 6 | 7 | {({ transitionStatus, exit, entry }) => { 8 | const mount = ['entering', 'entered'].includes(transitionStatus) 9 | const seconds = mount ? entry.length : exit.length 10 | 11 | return ( 12 | 21 | {props =>
{text}
} 22 |
23 | ) 24 | }} 25 |
26 | ) 27 | 28 | const SpringLink = ({ to, children }) => ( 29 | 30 | {children} 31 | 32 | ) 33 | 34 | export { MySpring, SpringLink } 35 | -------------------------------------------------------------------------------- /example/src/components/header.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'gatsby-plugin-transition-link' 3 | 4 | import GithubLink from './GithubLink' 5 | 6 | const Header = ({ siteTitle, locationState }) => { 7 | const headerColor = 8 | locationState && locationState.headerBgColor 9 | ? locationState.headerBgColor 10 | : 'rebeccapurple' 11 | return ( 12 |
18 |
26 |

27 | 34 | {siteTitle} 35 | 36 |

37 |
38 | 39 | 40 |
41 | ) 42 | } 43 | 44 | export default Header 45 | -------------------------------------------------------------------------------- /src/components/Layout.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | const React = require('react') 3 | 4 | const preferDefault = m => (m && m.default) || m 5 | let Layout = false 6 | 7 | if ( 8 | typeof TL__GATSBY_LAYOUT_COMPONENT_PATH !== `undefined` && 9 | !!TL__GATSBY_LAYOUT_COMPONENT_PATH 10 | ) { 11 | try { 12 | Layout = preferDefault(require(TL__GATSBY_LAYOUT_COMPONENT_PATH)) 13 | } catch (e) { 14 | if (e.toString().indexOf(`Error: Cannot find module`) !== -1) { 15 | throw new Error( 16 | `Couldn't find layout component at "${TL__GATSBY_LAYOUT_COMPONENT_PATH}.\n\n` + 17 | `Please create layout component in that location or specify path to layout component in gatsby-config.js` 18 | ) 19 | } else { 20 | // Logging the error for debugging older browsers as there is no way 21 | // to wrap the thrown error in a try/catch. 22 | console.error(e) 23 | throw e 24 | } 25 | } 26 | } 27 | 28 | const LayoutComponent = ({ children, ...props }) => { 29 | if (Layout) { 30 | return {children} 31 | } else { 32 | return children 33 | } 34 | } 35 | 36 | export { LayoutComponent } 37 | -------------------------------------------------------------------------------- /example/src/pages/programmatic/b.js: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react' 2 | import { useTriggerTransition } from 'gatsby-plugin-transition-link' 3 | import Layout from '../../components/layout' 4 | import { fade } from '../../components/programmatic' 5 | 6 | const B = () => { 7 | const triggerTransition = useTriggerTransition({ 8 | exit: { 9 | length: 1, 10 | trigger: ({ exit, node }) => fade({ exit, node, direction: 'out' }), 11 | }, 12 | entry: { 13 | length: 0.5, 14 | delay: 0.5, 15 | trigger: ({ exit, node }) => fade({ exit, node, direction: 'in' }), 16 | }, 17 | }) 18 | const progrToA = () => { 19 | setTimeout(() => { 20 | triggerTransition({ 21 | to: '/programmatic/a', 22 | }) 23 | }, 500) 24 | } 25 | return ( 26 | 27 |

Hello programmatic people

28 | e.preventDefault()} 32 | > 33 | Hover over me and go to page A programmatically in 500ms 34 | 35 |
36 | ) 37 | } 38 | 39 | export default B 40 | -------------------------------------------------------------------------------- /example/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 gatsbyjs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "description": "Gatsby default starter", 4 | "version": "1.0.0", 5 | "author": "Kyle Mathews ", 6 | "dependencies": { 7 | "gatsby": "^2.24.80", 8 | "gatsby-plugin-manifest": "^2.0.6", 9 | "gatsby-plugin-ngrok-tunneling": "^1.1.5", 10 | "gatsby-plugin-offline": "^3.0.34", 11 | "gatsby-plugin-react-helmet": "^3.0.0", 12 | "gatsby-plugin-remove-serviceworker": "^1.0.0", 13 | "gatsby-plugin-transition-link": "1.17.8", 14 | "gsap": "^3.1.1", 15 | "ngrok": "^3.2.7", 16 | "react": "^16.8.5", 17 | "react-dom": "^16.8.5", 18 | "react-helmet": "^5.2.0", 19 | "react-pose": "^4.0.1", 20 | "react-spring": "^7.2.8" 21 | }, 22 | "keywords": [ 23 | "gatsby" 24 | ], 25 | "license": "MIT", 26 | "scripts": { 27 | "build": "gatsby build", 28 | "develop": "gatsby develop", 29 | "format": "prettier --write '**/*.js'", 30 | "test": "echo \"Error: no test specified\" && exit 1" 31 | }, 32 | "devDependencies": { 33 | "prettier": "^1.14.2" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/gatsbyjs/gatsby-starter-default" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/hooks/useTriggerTransition.js: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react' 2 | import { navigate } from 'gatsby' 3 | import { Context } from '../context/createTransitionContext' 4 | import { triggerTransition } from '../utils/triggerTransition' 5 | 6 | const useTriggerTransition = defaultOptions => { 7 | const context = useContext(Context) 8 | const programmaticallyTriggerTransition = calledOptions => { 9 | // allow passing an event directly instead of options 10 | if ( 11 | calledOptions instanceof Event || 12 | (calledOptions.nativeEvent && 13 | calledOptions.nativeEvent instanceof Event) 14 | ) { 15 | calledOptions = { 16 | event: calledOptions, 17 | } 18 | } 19 | 20 | const options = { 21 | ...defaultOptions, 22 | ...calledOptions, 23 | } 24 | 25 | if (context.disableAnimation) { 26 | // If the user has set their browser or OS to prefers-reduced-motion 27 | // we should respect that. 28 | if (options.event) { 29 | options.event.persist() 30 | options.event.preventDefault() 31 | } 32 | navigate(options.to) 33 | } else { 34 | triggerTransition({ 35 | ...context, 36 | ...options, 37 | }) 38 | } 39 | } 40 | return programmaticallyTriggerTransition 41 | } 42 | 43 | export { useTriggerTransition } 44 | -------------------------------------------------------------------------------- /src/AniLink/Fade.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TransitionLink from '../' 3 | import gsap from 'gsap' 4 | 5 | const fade = ({ exit: { length }, node, direction }) => { 6 | const duration = direction === 'out' ? length + length / 4 : length 7 | const opacity = direction === 'in' ? 1 : 0 8 | const scrollTop = 9 | (document.scrollingElement && document.scrollingElement.scrollTop) || 10 | document.body.scrollTop || 11 | window.pageYOffset 12 | 13 | const holdPosition = 14 | direction === 'out' 15 | ? { 16 | overflowY: 'hidden', 17 | height: '100vh', 18 | scrollTop: scrollTop, 19 | } 20 | : {} 21 | 22 | return gsap.timeline().set(node, holdPosition) 23 | .fromTo(node, { opacity: !opacity }, { opacity: opacity, duration }) 24 | } 25 | 26 | export default function Fade({ 27 | exit, 28 | entry, 29 | fade: removedProp, 30 | duration, 31 | ...props 32 | }) { 33 | const length = duration || 0.8 34 | 35 | return ( 36 | 41 | fade({ exit, node, direction: 'out' }), 42 | }} 43 | entry={{ 44 | length: 0, 45 | trigger: ({ exit, node }) => 46 | fade({ exit, node, direction: 'in' }), 47 | }} 48 | {...props}> 49 | {props.children} 50 | 51 | ) 52 | } 53 | -------------------------------------------------------------------------------- /example/src/components/GithubLink.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default function GithubLink() { 4 | return ( 5 | 11 | 17 | GitHub icon 18 | 19 | 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /src/components/TransitionPortal.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | const portalRoot = typeof document !== `undefined` ? document.body : false 5 | 6 | const PortalContainer = props => { 7 | const zIndex = (function(level) { 8 | switch (level) { 9 | case 'bottom': 10 | return 1000 11 | case 'top': 12 | return 1200 13 | default: 14 | return 1100 15 | } 16 | })(props.level) 17 | 18 | return ( 19 |
27 | {props.children} 28 |
29 | ) 30 | } 31 | 32 | export default class TransitionPortal extends Component { 33 | constructor() { 34 | super() 35 | this.el = 36 | typeof document !== `undefined` 37 | ? document.createElement('section') 38 | : false 39 | } 40 | 41 | componentDidMount = () => { 42 | portalRoot && portalRoot.appendChild(this.el) 43 | } 44 | 45 | componentWillUnmount = () => { 46 | portalRoot && portalRoot.removeChild(this.el) 47 | } 48 | 49 | render() { 50 | return this.el && portalRoot 51 | ? ReactDOM.createPortal( 52 | <> 53 | {portalRoot && ( 54 | 57 | {this.props.children} 58 | 59 | )} 60 | , 61 | this.el 62 | ) 63 | : null 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /example/src/components/layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import Helmet from 'react-helmet' 4 | import { StaticQuery, graphql } from 'gatsby' 5 | 6 | import Header from './header' 7 | import './layout.css' 8 | 9 | const Layout = ({ children, theme, style }) => ( 10 |
11 | ( 22 |
23 | 30 | 31 | 32 |
33 |
41 | {children} 42 |
43 |
44 | )} 45 | /> 46 |
47 | ) 48 | 49 | Layout.propTypes = { 50 | children: PropTypes.node.isRequired, 51 | } 52 | 53 | export default Layout 54 | -------------------------------------------------------------------------------- /example/src/components/Poses/ExamplePose.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import posed from 'react-pose' 3 | import TransitionLink, { TransitionState } from 'gatsby-plugin-transition-link' 4 | 5 | const Box = posed.div({ 6 | hidden: { opacity: 0 }, 7 | visible: { opacity: 1 }, 8 | }) 9 | 10 | export default class Example extends React.Component { 11 | render() { 12 | const { props } = this 13 | 14 | return ( 15 | <> 16 | 17 | {({ transitionStatus: status }) => { 18 | return ( 19 | 24 | 32 | 33 | ) 34 | }} 35 | 36 |