1
0
mirror of https://github.com/kremalicious/portfolio.git synced 2024-12-23 01:29:41 +01:00

Merge pull request #30 from kremalicious/feature/page-transitions

prototype page transitions
This commit is contained in:
Matthias Kretschmann 2018-09-14 21:38:14 +02:00 committed by GitHub
commit 7c088ad766
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 191 additions and 188 deletions

View File

@ -1,8 +1,10 @@
require('./src/styles/global.scss') import wrapPageElementWithTransition from './src/helpers/wrapPageElement'
import './src/styles/global.scss'
exports.onClientEntry = () => { // IntersectionObserver polyfill for gatsby-image (Safari, IE)
// IntersectionObserver polyfill for gatsby-image (Safari, IE) if (typeof window !== 'undefined' && !window.IntersectionObserver) {
if (typeof window.IntersectionObserver === 'undefined') { import('intersection-observer')
require('intersection-observer')
}
} }
// Page Transitions & Layout
export const wrapPageElement = wrapPageElementWithTransition

4
gatsby-ssr.js Normal file
View File

@ -0,0 +1,4 @@
import wrapPageElementWithTransition from './src/helpers/wrapPageElement'
// Page Transitions & Layout
export const wrapPageElement = wrapPageElementWithTransition

View File

@ -21,7 +21,7 @@
}, },
"dependencies": { "dependencies": {
"file-saver": "^1.3.8", "file-saver": "^1.3.8",
"gatsby": "^2.0.0-rc.21", "gatsby": "^2.0.0-rc.24",
"gatsby-image": "^2.0.0-rc.2", "gatsby-image": "^2.0.0-rc.2",
"gatsby-plugin-manifest": "^2.0.2-rc.1", "gatsby-plugin-manifest": "^2.0.2-rc.1",
"gatsby-plugin-matomo": "^0.5.0", "gatsby-plugin-matomo": "^0.5.0",
@ -29,7 +29,7 @@
"gatsby-plugin-react-helmet": "^3.0.0-rc.1", "gatsby-plugin-react-helmet": "^3.0.0-rc.1",
"gatsby-plugin-sass": "^2.0.0-rc.2", "gatsby-plugin-sass": "^2.0.0-rc.2",
"gatsby-plugin-sharp": "^2.0.0-rc.7", "gatsby-plugin-sharp": "^2.0.0-rc.7",
"gatsby-plugin-sitemap": "^2.0.0-rc.1", "gatsby-plugin-sitemap": "^2.0.0-rc.2",
"gatsby-source-filesystem": "^2.0.1-rc.6", "gatsby-source-filesystem": "^2.0.1-rc.6",
"gatsby-transformer-json": "^2.1.1-rc.6", "gatsby-transformer-json": "^2.1.1-rc.6",
"gatsby-transformer-sharp": "^2.1.1-rc.3", "gatsby-transformer-sharp": "^2.1.1-rc.3",
@ -39,11 +39,11 @@
"intersection-observer": "^0.5.0", "intersection-observer": "^0.5.0",
"js-yaml": "^3.12.0", "js-yaml": "^3.12.0",
"node-sass": "^4.9.3", "node-sass": "^4.9.3",
"react": "^16.5.0", "react": "^16.5.1",
"react-dom": "^16.5.0", "react-dom": "^16.5.1",
"react-helmet": "^5.2.0", "react-helmet": "^5.2.0",
"react-markdown": "^3.6.0", "react-markdown": "^3.6.0",
"react-transition-group": "^2.4.0", "react-pose": "^3.3.0",
"vcf": "^2.0.1" "vcf": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,28 +1,41 @@
import React, { Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import posed, { PoseGroup } from 'react-pose'
import { fadeIn } from './atoms/Transitions'
import Head from './molecules/Head' import Head from './molecules/Head'
import Header from './organisms/Header' import Header from './organisms/Header'
import Footer from './organisms/Footer' import Footer from './organisms/Footer'
import styles from './Layout.module.scss' import styles from './Layout.module.scss'
const Layout = ({ children, location }) => { const timeout = 250
const isHomepage = location.pathname === '/'
return ( export default class Layout extends PureComponent {
<Fragment> static propTypes = {
<Head /> children: PropTypes.any.isRequired,
<Header isHomepage={isHomepage} /> location: PropTypes.object.isRequired
}
<main className={styles.screen}>{children}</main> render() {
const { children, location } = this.props
const isHomepage = location.pathname === '/'
<Footer /> const RoutesContainer = posed.div(fadeIn)
</Fragment>
) return (
<Fragment>
<Head />
<PoseGroup animateOnMount={true}>
<RoutesContainer
key={location.pathname}
delay={timeout}
delayChildren={timeout}
>
<Header isHomepage={isHomepage} />
<main className={styles.screen}>{children}</main>
</RoutesContainer>
</PoseGroup>
<Footer />
</Fragment>
)
}
} }
Layout.propTypes = {
children: PropTypes.any.isRequired,
location: PropTypes.object.isRequired
}
export default Layout

View File

@ -1,21 +0,0 @@
import React from 'react'
import CSSTransition from 'react-transition-group/CSSTransition'
import './Animations.scss'
const Animation = props => <CSSTransition appear={true} in={true} {...props} />
export const FadeIn = props => (
<Animation
classNames="fadein"
timeout={{ enter: 200, exit: 200, appear: 200 }}
{...props}
/>
)
export const MoveIn = props => (
<Animation
classNames="movein"
timeout={{ enter: 300, exit: 200, appear: 300 }}
{...props}
/>
)

View File

@ -1,43 +0,0 @@
.fadein-appear,
.fadein-enter {
opacity: .01;
&.fadein-appear-active,
&.fadein-enter-active {
opacity: 1;
transition: 200ms ease-in;
}
}
.fadein-exit {
opacity: 1;
transition: 200ms ease-in;
&.fadein-exit-active {
opacity: .01;
}
}
.movein-appear,
.movein-enter {
opacity: .01;
transform: translate3d(0, 3rem, 0);
&.movein-appear-active,
&.movein-enter-active {
opacity: 1;
transform: translate3d(0, 0, 0);
transition: 300ms ease-out;
}
}
.movein-exit {
opacity: 1;
transform: translate3d(0, 0, 0);
transition: 100ms ease-out;
&.movein-exit-active {
opacity: .01;
transform: translate3d(0, 3rem, 0);
}
}

View File

@ -1,6 +1,9 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { StaticQuery, graphql } from 'gatsby' import { StaticQuery, graphql } from 'gatsby'
import posed, { PoseGroup } from 'react-pose'
import { moveInBottom } from '../atoms/Transitions'
import Logo from '../svg/Logo' import Logo from '../svg/Logo'
import styles from './LogoUnit.module.scss' import styles from './LogoUnit.module.scss'
@ -13,6 +16,8 @@ const query = graphql`
} }
` `
const Animation = posed.div(moveInBottom)
class LogoUnit extends PureComponent { class LogoUnit extends PureComponent {
state = { minimal: false } state = { minimal: false }
@ -43,16 +48,20 @@ class LogoUnit extends PureComponent {
const { minimal } = this.state const { minimal } = this.state
return ( return (
<div className={minimal ? styles.minimal : styles.logounit}> <PoseGroup animateOnMount={true}>
<Logo className={styles.logounit__logo} /> <Animation>
<h1 className={styles.logounit__title}> <div className={minimal ? styles.minimal : styles.logounit}>
{meta.title.toLowerCase()} <Logo className={styles.logounit__logo} />
</h1> <h1 className={styles.logounit__title}>
<p className={styles.logounit__description}> {meta.title.toLowerCase()}
<span>{'{ '}</span> {meta.tagline.toLowerCase()}{' '} </h1>
<span>{' }'}</span> <p className={styles.logounit__description}>
</p> <span>{'{ '}</span> {meta.tagline.toLowerCase()}{' '}
</div> <span>{' }'}</span>
</p>
</div>
</Animation>
</PoseGroup>
) )
}} }}
/> />

View File

@ -25,13 +25,11 @@
margin-right: $spacer / 4; margin-right: $spacer / 4;
color: $brand-main; color: $brand-main;
line-height: $line-height; line-height: $line-height;
transition: color .2s ease-out;
} }
.logounit__description { .logounit__description {
font-size: $font-size-h4; font-size: $font-size-h4;
color: $brand-grey; color: $brand-grey;
transition: color .2s ease-out;
:global(.dark) & { :global(.dark) & {
color: $brand-grey-light; color: $brand-grey-light;

View File

@ -0,0 +1,30 @@
export const fadeIn = {
enter: {
opacity: 1
},
exit: {
opacity: 0
}
}
export const moveInTop = {
enter: {
y: 0,
transition: { type: 'spring' }
},
exit: {
y: '-2rem',
transition: { type: 'spring' }
}
}
export const moveInBottom = {
enter: {
y: 0,
transition: { type: 'spring' }
},
exit: {
y: '2rem',
transition: { type: 'spring' }
}
}

View File

@ -1,7 +1,8 @@
import React, { Fragment, PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { StaticQuery, graphql } from 'gatsby' import { StaticQuery, graphql } from 'gatsby'
import { MoveIn } from '../atoms/Animations' import posed, { PoseGroup } from 'react-pose'
import { fadeIn } from '../atoms/Transitions'
import styles from './Availability.module.scss' import styles from './Availability.module.scss'
const query = graphql` const query = graphql`
@ -16,6 +17,8 @@ const query = graphql`
} }
` `
const Animation = posed.aside(fadeIn)
export default class Availability extends PureComponent { export default class Availability extends PureComponent {
static propTypes = { hide: PropTypes.bool } static propTypes = { hide: PropTypes.bool }
@ -28,25 +31,23 @@ export default class Availability extends PureComponent {
const { status, available, unavailable } = availability const { status, available, unavailable } = availability
return ( return (
<Fragment> !this.props.hide && (
{!this.props.hide && ( <PoseGroup animateOnMount={true}>
<MoveIn> <Animation
<aside className={
className={ status
status ? `${styles.availability} ${styles.available}`
? `${styles.availability} ${styles.available}` : `${styles.availability}`
: `${styles.availability}` }
} >
> <p
<p dangerouslySetInnerHTML={{
dangerouslySetInnerHTML={{ __html: status ? available : unavailable
__html: status ? available : unavailable }}
}} />
/> </Animation>
</aside> </PoseGroup>
</MoveIn> )
)}
</Fragment>
) )
}} }}
/> />

View File

@ -8,7 +8,6 @@
z-index: 2; z-index: 2;
padding: $spacer / 2; padding: $spacer / 2;
display: block; display: block;
transition: opacity .2s ease-out;
:global(.dark) & { :global(.dark) & {
color: $text-color-light--dark; color: $text-color-light--dark;

View File

@ -1,7 +1,8 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { StaticQuery, graphql } from 'gatsby' import { StaticQuery, graphql } from 'gatsby'
import { FadeIn } from '../atoms/Animations' import posed, { PoseGroup } from 'react-pose'
import { moveInTop } from '../atoms/Transitions'
import Email from '../svg/Email' import Email from '../svg/Email'
import Blog from '../svg/Blog' import Blog from '../svg/Blog'
@ -43,6 +44,8 @@ const NetworkIcon = props => {
} }
} }
const Animation = posed.aside(moveInTop)
export default class Network extends PureComponent { export default class Network extends PureComponent {
static propTypes = { static propTypes = {
minimal: PropTypes.bool, minimal: PropTypes.bool,
@ -78,16 +81,16 @@ export default class Network extends PureComponent {
return ( return (
!this.props.hide && ( !this.props.hide && (
<FadeIn> <PoseGroup animateOnMount={true}>
<aside className={this.state.classes}> <Animation className={this.state.classes}>
{Object.keys(meta.social).map((key, i) => ( {Object.keys(meta.social).map((key, i) => (
<a className={styles.link} href={meta.social[key]} key={i}> <a className={styles.link} href={meta.social[key]} key={i}>
<NetworkIcon title={key} className={icons.icon} /> <NetworkIcon title={key} className={icons.icon} />
<span className={styles.title}>{key}</span> <span className={styles.title}>{key}</span>
</a> </a>
))} ))}
</aside> </Animation>
</FadeIn> </PoseGroup>
) )
) )
}} }}

View File

@ -1,10 +1,15 @@
import React, { PureComponent, Fragment } from 'react' import React, { PureComponent, Fragment } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Helmet from 'react-helmet' import Helmet from 'react-helmet'
import posed, { PoseGroup } from 'react-pose'
import { fadeIn } from '../atoms/Transitions'
import Day from '../svg/Day' import Day from '../svg/Day'
import Night from '../svg/Night' import Night from '../svg/Night'
import styles from './ThemeSwitch.module.scss' import styles from './ThemeSwitch.module.scss'
const Animation = posed.aside(fadeIn)
const ThemeToggle = ({ dark }) => ( const ThemeToggle = ({ dark }) => (
<span id="toggle" className={styles.checkboxContainer} aria-live="assertive"> <span id="toggle" className={styles.checkboxContainer} aria-live="assertive">
<Day className={dark ? null : 'active'} /> <Day className={dark ? null : 'active'} />
@ -60,20 +65,22 @@ export default class ThemeSwitch extends PureComponent {
<Helmet> <Helmet>
<body className={this.isDark() ? 'dark' : null} /> <body className={this.isDark() ? 'dark' : null} />
</Helmet> </Helmet>
<aside className={styles.themeSwitch}> <PoseGroup animateOnMount={true}>
<label className={styles.checkbox}> <Animation className={styles.themeSwitch}>
<span className={styles.label}>Toggle Night Mode</span> <label className={styles.checkbox}>
<input <span className={styles.label}>Toggle Night Mode</span>
onChange={this.handleChange} <input
type="checkbox" onChange={this.handleChange}
name="toggle" type="checkbox"
value="toggle" name="toggle"
aria-describedby="toggle" value="toggle"
checked={this.isDark()} aria-describedby="toggle"
/> checked={this.isDark()}
<ThemeToggle dark={this.isDark()} /> />
</label> <ThemeToggle dark={this.isDark()} />
</aside> </label>
</Animation>
</PoseGroup>
</Fragment> </Fragment>
) )
} }

View File

@ -0,0 +1,8 @@
import React from 'react'
import Layout from '../components/Layout'
const wrapPageElement = ({ element, props }) => (
<Layout {...props}>{element}</Layout>
)
export default wrapPageElement

View File

@ -2,7 +2,6 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Link } from 'gatsby' import { Link } from 'gatsby'
import giphyAPI from 'giphy-js-sdk-core' import giphyAPI from 'giphy-js-sdk-core'
import Layout from '../components/Layout'
import Content from '../components/atoms/Content' import Content from '../components/atoms/Content'
import Button from '../components/atoms/Button' import Button from '../components/atoms/Button'
import './404.scss' import './404.scss'
@ -42,24 +41,22 @@ export default class NotFound extends Component {
render() { render() {
return ( return (
<Layout location={this.props.location}> <Content className="content content--404">
<Content className="content content--404"> <h1>Shenanigans, page not found.</h1>
<h1>Shenanigans, page not found.</h1> <p>
<p> You might want to check the url, or{' '}
You might want to check the url, or{' '} <Link to={'/'}>go back to the homepage</Link>. Or just check out some
<Link to={'/'}>go back to the homepage</Link>. Or just check out cat fail gifs, entirely your choice.
some cat fail gifs, entirely your choice. </p>
</p>
<video className="gif" src={this.state.gif} autoPlay loop /> <video className="gif" src={this.state.gif} autoPlay loop />
<div> <div>
<Button onClick={this.handleClick}> <Button onClick={this.handleClick}>
Show me another cat fail gif Show me another cat fail gif
</Button> </Button>
</div> </div>
</Content> </Content>
</Layout>
) )
} }
} }

View File

@ -1,33 +1,30 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Link, graphql } from 'gatsby' import { Link, graphql } from 'gatsby'
import Layout from '../components/Layout'
import ProjectImage from '../components/atoms/ProjectImage' import ProjectImage from '../components/atoms/ProjectImage'
import FullWidth from '../components/atoms/FullWidth' import FullWidth from '../components/atoms/FullWidth'
import styles from './index.module.scss' import styles from './index.module.scss'
const Home = ({ data, location }) => { const Home = ({ data }) => {
const projects = data.allProjectsYaml.edges const projects = data.allProjectsYaml.edges
return ( return (
<Layout location={location}> <FullWidth>
<FullWidth> <div className={styles.projects}>
<div className={styles.projects}> {projects.map(({ node }) => {
{projects.map(({ node }) => { const { slug, title, img } = node
const { slug, title, img } = node
return ( return (
<article className={styles.project} key={slug}> <article className={styles.project} key={slug}>
<Link to={slug}> <Link to={slug}>
<h1 className={styles.title}>{title}</h1> <h1 className={styles.title}>{title}</h1>
<ProjectImage fluid={img.childImageSharp.fluid} alt={title} /> <ProjectImage fluid={img.childImageSharp.fluid} alt={title} />
</Link> </Link>
</article> </article>
) )
})} })}
</div> </div>
</FullWidth> </FullWidth>
</Layout>
) )
} }

View File

@ -29,7 +29,7 @@ body {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
min-height: 100vh; min-height: 100vh;
background: $body-background-color; background: $body-background-color;
transition: background .6s $easing; transition: background .2s $easing;
&.dark { &.dark {
background-color: $body-background-color--dark; background-color: $body-background-color--dark;

View File

@ -1,9 +1,8 @@
import React from 'react' import React, { Fragment } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Helmet from 'react-helmet' import Helmet from 'react-helmet'
import ReactMarkdown from 'react-markdown' import ReactMarkdown from 'react-markdown'
import { graphql } from 'gatsby' import { graphql } from 'gatsby'
import Layout from '../components/Layout'
import Content from '../components/atoms/Content' import Content from '../components/atoms/Content'
import FullWidth from '../components/atoms/FullWidth' import FullWidth from '../components/atoms/FullWidth'
import ProjectImage from '../components/atoms/ProjectImage' import ProjectImage from '../components/atoms/ProjectImage'
@ -31,7 +30,7 @@ const ProjectImages = ({ projectImages, title }) => (
</FullWidth> </FullWidth>
) )
const Project = ({ data, location }) => { const Project = ({ data }) => {
const project = data.projectsYaml const project = data.projectsYaml
const projectImages = data.projectImages.edges const projectImages = data.projectImages.edges
const { title, links, techstack } = project const { title, links, techstack } = project
@ -39,7 +38,7 @@ const Project = ({ data, location }) => {
const descriptionWithLineBreaks = description.split('\n').join('\n\n') const descriptionWithLineBreaks = description.split('\n').join('\n\n')
return ( return (
<Layout location={location}> <Fragment>
<Helmet title={title} /> <Helmet title={title} />
<SEO project={project} /> <SEO project={project} />
@ -57,7 +56,7 @@ const Project = ({ data, location }) => {
</article> </article>
<ProjectNav slug={project.slug} /> <ProjectNav slug={project.slug} />
</Layout> </Fragment>
) )
} }