1
0
mirror of https://github.com/kremalicious/portfolio.git synced 2024-12-22 09:13:19 +01:00

Merge pull request #10 from kremalicious/feature/gatsby-v2

Update to Gatsby v2
This commit is contained in:
Matthias Kretschmann 2018-07-14 02:38:58 +02:00 committed by GitHub
commit eba94c3877
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 635 additions and 1163 deletions

View File

@ -1,2 +1,5 @@
exclude_patterns:
- src/lib/vcf/
version: "2"
checks:
method-lines:
config:
threshold: 50 # Gatsby's StaticQuery makes render functions pretty long

View File

@ -18,9 +18,6 @@
"node": true,
"es6": true
},
"globals": {
"graphql": true
},
"rules": {
"quotes": ["error", "single"],
"semi": ["error", "never"],

1
.gitignore vendored
View File

@ -11,3 +11,4 @@ yarn-debug.log*
yarn.lock
package-lock.json
plugins/gatsby-plugin-matomo
src/components/svg

View File

@ -1,5 +1,4 @@
node_modules/
.cache/
src/lib/vcf
static/
public/

View File

@ -7,10 +7,6 @@ cache:
- public
install:
- pip install --user awscli
- export PATH=$PATH:$HOME/.local/bin
- npm i -g gatsby-cli
- export PATH="$PATH:/usr/local/bin/gatsby"
- npm i
script:
@ -18,9 +14,9 @@ script:
- npm run build
after_success:
- pip install --user awscli
- export PATH=$PATH:$HOME/.local/bin
- npm run deploy
notifications:
email: false
slack:
secure: v/sqm2fgXTi7RJIjVsaJz1i3bV/WNFyTkLLto2p/fXjoWHVWJBdodANTTH+9lgkbft6JdBcU4WDLDkLP8VKlR9JffFc1DvbxGmF3VTfP2kJcnCbo+koLHBtzR1oRwDB1aulp6+II0ROPS9OyOv+Xx45bj4jOt9y/r0Qcp2V5fKvSUEwEecxaTmvBEn3fvZ9COSgL8ufq9hCCT/6r3Bxl0Pp28Fj23pLwuM6tCKjriM4w5WY0xY2Sv4unz6u7LNec5DZH3QZPH4/pdRVBmC3rxt3quw8IMt6NNXG3ODUPRqS7GB44Q+k8L+15Dn5Cx22o92wULpXKTDvHmLLj2lej50V0cyoj7U4SbDkVOohUG23fn10/AzvZogYA2ugf3YXKJCBbyyloJV6jFnZAAvKRSD8B5PFnuhf+uLEAfX9Of1FLNO2c7XIQa86ht5xrpsRFCzNCFhtyHNc8pXwSv7EUv7K2bccCGKv0a7DxYquP0OB3OSkI6tvRc3mvYpe5k2WKNHFRByud14ywGxMn0FYBwtsHbW3QJ/z/WaFYS2hJXjtpjHQW1wNDy+67UTiZFi1anovUHoZvkrIKtRqY1eyXPJB31TqPHWSX0er0IfdhfE3o9mMuoSVUip6S29eY7hhVk5S3IsK1YlPAFW8pa9OPiUBt8z+gbf8WN23qi61Bn7g=

View File

@ -19,8 +19,6 @@ RUN apk update && \
libpng-dev \
&& rm -rf /var/cache/apk/*
RUN npm install --global gatsby-cli --no-optional
RUN mkdir -p /portfolio
WORKDIR /portfolio
VOLUME /portfolio

View File

@ -15,19 +15,19 @@
## Table of Contents
* [Features](#features)
* [One data file to rule all pages](#one-data-file-to-rule-all-pages)
* [Theme switcher](#theme-switcher)
* [SEO component](#seo-component)
* [Client-side vCard creation](#client-side-vcard-creation)
* [Matomo (formerly Piwik) analytics tracking](#matomo-formerly-piwik-analytics-tracking)
* [Project images](#project-images)
* [Importing SVG assets](#importing-svg-assets)
* [Development](#development)
* [Linting](#linting)
* [Add a new project](#add-a-new-project)
* [Deployment](#deployment)
* [Licenses](#licenses)
- [Features](#features)
- [One data file to rule all pages](#one-data-file-to-rule-all-pages)
- [Theme switcher](#theme-switcher)
- [SEO component](#seo-component)
- [Client-side vCard creation](#client-side-vcard-creation)
- [Matomo (formerly Piwik) analytics tracking](#matomo-formerly-piwik-analytics-tracking)
- [Project images](#project-images)
- [Importing SVG assets](#importing-svg-assets)
- [Development](#development)
- [Linting](#linting)
- [Add a new project](#add-a-new-project)
- [Deployment](#deployment)
- [Licenses](#licenses)
---
@ -79,10 +79,10 @@ All project images use one single component defined in [`src/components/atoms/Pr
### Importing SVG assets
Makes use of `gatsby-plugin-svgr` so SVG assets can be imported like so:
All SVG assets under `src/images/` will be converted to React components before every build. Makes use of `SVGR` so SVG assets can be imported like so:
```js
import { ReactComponent as Logo } from '../images/logo.svg'
import Logo from './components/svg/Logo'
```
## Development
@ -133,8 +133,8 @@ portfolio-SLUG-03.png
Automatic deployments are triggered upon successful tests & builds on Travis:
* push to `master` initiates a live deployment
* any Pull Request, and subsequent pushes to it, initiates a beta deployment
- push to `master` initiates a live deployment
- any Pull Request, and subsequent pushes to it, initiates a beta deployment
The deploy command simply calls the [`scripts/deploy.sh`](scripts/deploy.sh) script, syncing the contents of the `public/` folder to S3:

10
gatsby-browser.js Normal file
View File

@ -0,0 +1,10 @@
exports.onInitialClientRender = () => {
require('./src/styles/base.scss')
}
exports.onClientEntry = () => {
// IntersectionObserver polyfill for gatsby-image (Safari, IE)
if (typeof window.IntersectionObserver === 'undefined') {
require('intersection-observer')
}
}

View File

@ -2,20 +2,19 @@ const path = require('path')
const fs = require('fs')
const yaml = require('js-yaml')
const meta = yaml.load(fs.readFileSync('./data/meta.yml', 'utf8'))
const { url, matomoUrl, matomoSite } = meta
const { url, matomoSite, matomoUrl } = meta
module.exports = {
siteMetadata: {
siteUrl: `${url}`
},
plugins: [
'gatsby-plugin-react-next',
'gatsby-plugin-react-helmet',
'gatsby-transformer-yaml',
'gatsby-transformer-sharp',
'gatsby-plugin-sharp',
'gatsby-plugin-sitemap',
'gatsby-plugin-offline',
// 'gatsby-plugin-offline',
{
resolve: 'gatsby-transformer-json',
options: {
@ -59,14 +58,6 @@ module.exports = {
localScript: '/piwik.js'
}
},
{
resolve: 'gatsby-plugin-svgr',
options: {
icon: false,
viewBox: true
// see https://github.com/smooth-code/svgr for a list of all options
}
},
{
resolve: 'gatsby-plugin-favicon',
options: {

View File

@ -3,17 +3,28 @@ const path = require('path')
// Intersection Observer polyfill
// requires `npm install intersection-observer`
// https://github.com/gatsbyjs/gatsby/issues/2288#issuecomment-334467821
exports.modifyWebpackConfig = ({ config, stage }) => {
if (stage === 'build-html') {
config.loader('null', {
test: /intersection-observer/,
loader: 'null-loader'
})
}
}
// exports.onCreateWebpackConfig = ({ actions, loaders, stage }) => {
// const { setWebpackConfig } = actions
exports.createPages = ({ boundActionCreators, graphql }) => {
const { createPage } = boundActionCreators
// if (stage === 'build-html') {
// const nullRule = {
// test: /intersection-observer/,
// use: [loaders.null()]
// }
// setWebpackConfig({
// module: {
// rules: [nullRule]
// }
// })
// }
// }
//
// Create project pages from projects.yml
//
exports.createPages = async ({ actions, graphql }) => {
const { createPage } = actions
return new Promise((resolve, reject) => {
const template = path.resolve('src/templates/Project.jsx')
@ -26,44 +37,6 @@ exports.createPages = ({ boundActionCreators, graphql }) => {
node {
slug
}
previous {
title
slug
img {
id
childImageSharp {
sizes(maxWidth: 500, quality: 80) {
aspectRatio
src
srcSet
srcWebp
srcSetWebp
sizes
originalImg
originalName
}
}
}
}
next {
title
slug
img {
id
childImageSharp {
sizes(maxWidth: 500, quality: 80) {
aspectRatio
src
srcSet
srcWebp
srcSetWebp
sizes
originalImg
originalName
}
}
}
}
}
}
}
@ -72,21 +45,17 @@ exports.createPages = ({ boundActionCreators, graphql }) => {
reject(result.errors)
}
result.data.allProjectsYaml.edges.forEach(
({ node, previous, next }) => {
const slug = node.slug
result.data.allProjectsYaml.edges.forEach(({ node }) => {
const slug = node.slug
createPage({
path: slug,
component: template,
context: {
slug,
previous,
next
}
})
}
)
createPage({
path: slug,
component: template,
context: {
slug
}
})
})
resolve()
})

View File

@ -1,5 +1,5 @@
{
"name": "portfolio",
"name": "@kremalicious/portfolio",
"version": "0.1.0",
"homepage": "https://matthiaskretschmann.com",
"repository": "github:kremalicious/portfolio",
@ -9,57 +9,59 @@
"scripts": {
"lint:js": "eslint ./gatsby-*.{js,jsx} && eslint ./src/**/*.{js,jsx}",
"lint:css": "stylelint ./src/**/*.{css,scss}",
"lint": "npm run lint:js && npm run lint:css",
"build": "gatsby build",
"start": "docker-compose up",
"lint": "npm run svg && npm run lint:js && npm run lint:css",
"build": "npm run svg && ./node_modules/gatsby/dist/bin/gatsby.js build",
"start": "npm run svg && ./node_modules/gatsby/dist/bin/gatsby.js develop",
"format": "prettier --write 'src/**/*.{js,jsx}'",
"format:css": "prettier-stylelint --write --quiet 'src/**/*.{css,scss}'",
"test": "npm run lint",
"deploy": "./scripts/deploy.sh",
"new": "node ./scripts/new-project.js"
"new": "node ./scripts/new.js",
"svg": "./scripts/svg.sh"
},
"dependencies": {
"camel-case": "^3.0.0",
"file-saver": "^1.3.8",
"gatsby": "^1.9.270",
"gatsby-image": "^1.0.52",
"gatsby-link": "^1.6.44",
"gatsby-plugin-favicon": "^2.1.1",
"gatsby-plugin-matomo": "^0.4.0",
"gatsby-plugin-offline": "^1.0.18",
"gatsby-plugin-react-helmet": "^2.0.11",
"gatsby-plugin-react-next": "^1.0.11",
"gatsby-plugin-sass": "^1.0.26",
"gatsby-plugin-sharp": "^1.6.47",
"gatsby-plugin-sitemap": "^1.2.25",
"gatsby-plugin-svgr": "^1.0.1",
"gatsby-source-filesystem": "^1.5.38",
"gatsby-transformer-sharp": "^1.6.26",
"gatsby-transformer-yaml": "^1.5.17",
"giphy-js-sdk-core": "^1.0.3",
"gatsby": "^2.0.0-beta.35",
"gatsby-image": "^2.0.0-beta.6",
"gatsby-plugin-favicon": "github:TuckerWhitehouse/gatsby-plugin-favicon#gatsby-v2",
"gatsby-plugin-matomo": "^0.4.1",
"gatsby-plugin-offline": "^2.0.0-beta.3",
"gatsby-plugin-react-helmet": "next",
"gatsby-plugin-sass": "next",
"gatsby-plugin-sharp": "^2.0.0-beta.5",
"gatsby-plugin-sitemap": "next",
"gatsby-source-filesystem": "^2.0.1-beta.4",
"gatsby-transformer-json": "next",
"gatsby-transformer-sharp": "next",
"gatsby-transformer-yaml": "next",
"giphy-js-sdk-core": "^1.0.5",
"graphql": "^0.13.2",
"intersection-observer": "^0.5.0",
"js-yaml": "^3.12.0",
"node-sass": "^4.9.2",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-helmet": "^5.2.0",
"react-markdown": "^3.3.2",
"react-transition-group": "^2.3.1",
"react-markdown": "^3.3.4",
"react-transition-group": "^2.4.0",
"vcf": "^2.0.1"
},
"devDependencies": {
"babel-eslint": "^8.2.3",
"eslint": "^5.0.0",
"@svgr/cli": "^2.1.1",
"babel-eslint": "^8.2.6",
"eslint": "^5.1.0",
"eslint-config-prettier": "^2.9.0",
"eslint-loader": "^2.0.0",
"eslint-plugin-graphql": "^2.1.1",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-react": "^7.9.1",
"gatsby-transformer-json": "^1.0.19",
"eslint-plugin-prettier": "^2.6.2",
"eslint-plugin-react": "^7.10.0",
"ora": "^2.1.0",
"prepend": "^1.0.2",
"prettier": "^1.13.4",
"prettier": "^1.13.7",
"prettier-stylelint": "^0.4.2",
"slugify": "^1.3.0",
"stylelint": "^9.2.1",
"stylelint-config-css-modules": "^1.2.0",
"stylelint": "^9.3.0",
"stylelint-config-css-modules": "^1.3.0",
"stylelint-config-standard": "^18.2.0"
},
"browserslist": [

View File

@ -1,10 +1,8 @@
#!/usr/bin/env bash
set -e
export PATH="$PATH:/usr/local/bin/gatsby"
# echo "Running npm install..."
# npm install
# rm -rf ./public
gatsby develop --host 0.0.0.0
npm start

View File

@ -4,7 +4,7 @@ const prepend = require('prepend')
const slugify = require('slugify')
const ora = require('ora')
const templatePath = path.join(__dirname, 'new-project.yml')
const templatePath = path.join(__dirname, 'new.yml')
const template = fs.readFileSync(templatePath).toString()
const spinner = ora('Adding new project').start()

12
scripts/svg.sh Executable file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -e
SRC='./src/images'
OUT='./src/components/svg'
printf "Creating SVG components...\\n\\n"
# Usage: svgr [-d out-dir] [src-dir]
./node_modules/@svgr/cli/bin/svgr --icon -d $OUT $SRC
printf "\\n🎉 Successfully created SVG components\\n\\n"

80
src/components/Layout.jsx Normal file
View File

@ -0,0 +1,80 @@
import React, { Fragment } from 'react'
import PropTypes from 'prop-types'
import { StaticQuery, graphql } from 'gatsby'
import Head from './atoms/Head'
import Header from './organisms/Header'
import Footer from './organisms/Footer'
import styles from './Layout.module.scss'
const Layout = ({ children, location }) => {
const isHomepage = location.pathname === '/'
return (
<StaticQuery
query={graphql`
query {
# the data/meta.yml file
dataYaml {
title
tagline
description
url
email
avatar {
childImageSharp {
original: resize {
src
}
small: resize(width: 256) {
src
}
}
}
img {
childImageSharp {
resize(width: 980) {
src
}
}
}
social {
Email
Blog
Twitter
GitHub
Dribbble
}
availability {
status
}
gpg
addressbook
}
}
`}
render={data => {
const meta = data.dataYaml
return (
<Fragment>
<Head meta={meta} />
<Header meta={meta} isHomepage={isHomepage} />
<main className={styles.screen} location={location}>
{children}
</main>
<Footer meta={meta} />
</Fragment>
)
}}
/>
)
}
Layout.propTypes = {
children: PropTypes.any.isRequired,
location: PropTypes.object.isRequired
}
export default Layout

View File

@ -0,0 +1,6 @@
@import 'variables';
.screen {
flex: 1;
padding: $spacer;
}

View File

@ -0,0 +1,15 @@
import React from 'react'
import PropTypes from 'prop-types'
import styles from './Button.module.scss'
const Button = props => (
<a className={styles.button} {...props}>
{props.children}
</a>
)
Button.propTypes = {
children: PropTypes.node
}
export default Button

View File

@ -0,0 +1,48 @@
@import 'variables';
.button {
display: block;
width: 100%;
color: $brand-cyan;
text-align: center;
border-radius: .25rem;
padding: $spacer / 4 $spacer / 2;
transition-property: all;
background: rgba(#fff, .15);
border: .05rem solid rgba($brand-cyan, .75);
font-size: $font-size-small;
text-transform: uppercase;
svg {
fill: $brand-grey-light;
margin-right: $spacer / 3;
transition: .2s ease-out;
margin-bottom: -.1rem;
width: $font-size-small;
height: $font-size-small;
}
&:hover,
&:focus {
color: lighten($brand-cyan, 10%);
border-color: rgba(lighten($brand-cyan, 10%), .75);
transform: translate3d(0, -.1rem, 0);
box-shadow: 0 6px 10px rgba($brand-main, .1),
0 10px 25px rgba($brand-main, .05);
}
&:active {
transition: none;
background: rgba(#fff, .15);
}
:global(.dark) & {
background: darken($body-background-color--dark, 1%);
&:hover,
&:focus {
box-shadow: 0 6px 10px rgba(darken($brand-main, 20%), .1),
0 10px 25px rgba(darken($brand-main, 20%), .2);
}
}
}

View File

@ -5,7 +5,7 @@ import SEO from './SEO'
import Typekit from './Typekit'
const Head = ({ meta }) => {
const { title, tagline, typekitID } = meta
const { title, tagline } = meta
return (
<Fragment>
@ -17,7 +17,7 @@ const Head = ({ meta }) => {
<meta name="theme-color" content="#e7eef4" />
</Helmet>
{typekitID && <Typekit id={typekitID} />}
<Typekit />
<SEO meta={meta} />
</Fragment>

View File

@ -1,6 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { ReactComponent as Logo } from '../../images/logo.svg'
import Logo from '../svg/Logo'
import styles from './LogoUnit.module.scss'
const LogoUnit = ({ meta, minimal }) => {

View File

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

View File

@ -1,28 +1,28 @@
import React from 'react'
import PropTypes from 'prop-types'
import { graphql } from 'gatsby'
import Img from 'gatsby-image'
import 'intersection-observer'
import './ProjectImage.scss'
import styles from './ProjectImage.module.scss'
const ProjectImage = ({ sizes, alt }) => (
const ProjectImage = props => (
<Img
className="project__image"
outerWrapperClassName="project__image-wrap"
className={styles.project__image}
outerWrapperClassName={styles.project__imagewrap}
backgroundColor="#6b7f88"
sizes={sizes}
alt={alt}
fluid={props.fluid}
alt={props.alt}
/>
)
ProjectImage.propTypes = {
sizes: PropTypes.object.isRequired,
fluid: PropTypes.object.isRequired,
alt: PropTypes.string
}
export const projectImage = graphql`
fragment ProjectImageSizes on ImageSharp {
sizes(maxWidth: 1200, quality: 85) {
...GatsbyImageSharpSizes_noBase64
fragment ProjectImageFluid on ImageSharp {
fluid(maxWidth: 1200, quality: 85) {
...GatsbyImageSharpFluid_withWebp
}
}
`

View File

@ -1,6 +1,6 @@
@import 'variables';
.project__image-wrap {
.project__imagewrap {
max-height: 100vh;
margin-left: auto;
margin-right: auto;
@ -14,7 +14,7 @@
overflow: hidden;
}
.dark & {
:global(.dark) & {
box-shadow: 0 3px 5px rgba(darken($brand-main, 20%), .15),
0 5px 16px rgba(darken($brand-main, 20%), .15);
}

View File

@ -1,13 +1,14 @@
import React from 'react'
import PropTypes from 'prop-types'
import { StaticQuery, graphql } from 'gatsby'
import Helmet from 'react-helmet'
const TypekitScript = props => (
const TypekitScript = typekitID => (
<script>
{`
(function(d) {
var config = {
kitId: '${props.id}',
kitId: '${typekitID}',
scriptTimeout: 3000,
async: true
},
@ -17,13 +18,30 @@ const TypekitScript = props => (
</script>
)
const Typekit = props => (
<Helmet>
<link rel="dns-prefetch" href="https://use.typekit.net/" />
<link rel="dns-prefetch" href="https://p.typekit.net/" />
const Typekit = () => (
<StaticQuery
query={graphql`
query {
dataYaml {
typekitID
}
}
`}
render={data => {
const { typekitID } = data.dataYaml
{TypekitScript(props)}
</Helmet>
return (
typekitID && (
<Helmet>
<link rel="dns-prefetch" href="https://use.typekit.net/" />
<link rel="dns-prefetch" href="https://p.typekit.net/" />
{TypekitScript(typekitID)}
</Helmet>
)
)
}}
/>
)
export default Typekit

View File

@ -1,7 +1,7 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import FileSaver from 'file-saver'
import vCard from '../../lib/vcf/vcard'
import vCard from 'vcf'
class Vcard extends PureComponent {
constructor(props) {

View File

@ -1,5 +1,6 @@
import React, { Fragment, PureComponent } from 'react'
import PropTypes from 'prop-types'
import { StaticQuery, graphql } from 'gatsby'
import { MoveIn } from '../atoms/Animations'
import styles from './Availability.module.scss'
@ -9,35 +10,51 @@ class Availability extends PureComponent {
}
render() {
const { availability } = this.props.meta
const { status, available, unavailable } = availability
return (
<Fragment>
{!this.props.hide && (
<MoveIn>
<aside
className={
<StaticQuery
query={graphql`
query {
dataYaml {
availability {
status
? `${styles.availability} ${styles.available}`
: `${styles.availability} ${styles.unavailable}`
available
unavailable
}
>
<p
dangerouslySetInnerHTML={{
__html: status ? available : unavailable
}}
/>
</aside>
</MoveIn>
)}
</Fragment>
}
}
`}
render={data => {
const { availability } = data.dataYaml
const { status, available, unavailable } = availability
return (
<Fragment>
{!this.props.hide && (
<MoveIn>
<aside
className={
status
? `${styles.availability} ${styles.available}`
: `${styles.availability} ${styles.unavailable}`
}
>
<p
dangerouslySetInnerHTML={{
__html: status ? available : unavailable
}}
/>
</aside>
</MoveIn>
)}
</Fragment>
)
}}
/>
)
}
}
Availability.propTypes = {
meta: PropTypes.object,
hide: PropTypes.bool
}

View File

@ -2,11 +2,11 @@ import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { FadeIn } from '../atoms/Animations'
import { ReactComponent as Email } from '../../images/email.svg'
import { ReactComponent as Blog } from '../../images/blog.svg'
import { ReactComponent as Twitter } from '../../images/twitter.svg'
import { ReactComponent as GitHub } from '../../images/github.svg'
import { ReactComponent as Dribbble } from '../../images/dribbble.svg'
import Email from '../svg/Email'
import Blog from '../svg/Blog'
import Twitter from '../svg/Twitter'
import GitHub from '../svg/Github'
import Dribbble from '../svg/Dribbble'
import icons from '../atoms/Icons.module.scss'
import styles from './Networks.module.scss'

View File

@ -1,12 +1,13 @@
import React from 'react'
import PropTypes from 'prop-types'
import { ReactComponent as Link } from '../../images/link.svg'
import { ReactComponent as Download } from '../../images/download.svg'
import { ReactComponent as Info } from '../../images/info.svg'
import { ReactComponent as Styleguide } from '../../images/styleguide.svg'
import { ReactComponent as GitHub } from '../../images/github.svg'
import { ReactComponent as Dribbble } from '../../images/dribbble.svg'
import Button from '../atoms/Button'
import Link from '../svg/Link'
import Download from '../svg/Download'
import Info from '../svg/Info'
import Styleguide from '../svg/Styleguide'
import GitHub from '../svg/Github'
import Dribbble from '../svg/Dribbble'
import icons from '../atoms/Icons.module.scss'
import styles from './ProjectLinks.module.scss'
@ -42,10 +43,10 @@ const ProjectLinks = ({ links }) => (
return (
<li key={title}>
<a href={url}>
<Button href={url}>
<LinkIcon title={title} className={icons.icon} />
{title}
</a>
</Button>
</li>
)
})}

View File

@ -16,49 +16,6 @@
margin-left: $spacer / 2;
margin-bottom: $spacer / 2;
}
svg {
margin-right: $spacer / 3;
transition: .2s ease-out;
margin-bottom: -.1rem;
}
a {
display: block;
width: 100%;
color: $brand-cyan;
text-align: center;
border-radius: .25rem;
padding: $spacer / 4 $spacer / 2;
transition-property: all;
background: rgba(#fff, .15);
svg {
fill: $brand-grey-light;
}
&:hover,
&:focus {
transform: translate3d(0, -.1rem, 0);
box-shadow: 0 6px 10px rgba($brand-main, .1),
0 10px 25px rgba($brand-main, .1);
}
&:active {
transition: none;
background: rgba(#fff, .15);
}
:global(.dark) & {
background: darken($body-background-color--dark, 1%);
&:hover,
&:focus {
box-shadow: 0 6px 10px rgba(darken($brand-main, 20%), .1),
0 10px 25px rgba(darken($brand-main, 20%), .1);
}
}
}
}
.title {

View File

@ -1,14 +1,15 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Link from 'gatsby-link'
import { Link, graphql, StaticQuery } from 'gatsby'
import Img from 'gatsby-image'
import FullWidth from '../atoms/FullWidth'
import styles from './ProjectNav.module.scss'
const ProjectLink = ({ node }) => (
<Link className={styles.link} to={node.slug}>
<Img
className={styles.image}
sizes={node.img.childImageSharp.sizes}
fluid={node.img.childImageSharp.fluid}
alt={node.title}
/>
<h1 className={styles.title}>{node.title}</h1>
@ -19,52 +20,86 @@ class ProjectNav extends Component {
constructor(props) {
super(props)
this.state = {
scrolledToCurrent: false
}
this.scrollToCurrent = this.scrollToCurrent.bind(this)
}
componentDidMount() {
this.scrollToCurrent()
this.setState({ scrolledToCurrent: true })
}
componentDidUpdate() {
this.scrollToCurrent()
}
componentWillUnmount() {
this.setState({ scrolledToCurrent: false })
}
scrollToCurrent = () => {
const current = this.currentItem
const currentLeft = current.getBoundingClientRect().left
const currentWidth = current.clientWidth
const finalPosition = currentLeft - window.innerWidth / 2 + currentWidth / 2
this.scrollContainer.scrollLeft = finalPosition
this.scrollContainer.scrollLeft += finalPosition
}
render() {
const { projects, project } = this.props
const { slug } = this.props
return (
<nav
className={styles.projectNav}
ref={node => {
this.scrollContainer = node
}}
>
{projects.map(({ node }) => {
const current = node.slug === project.slug
<StaticQuery
query={graphql`
query {
allProjectsYaml {
edges {
node {
title
slug
img {
childImageSharp {
fluid(maxWidth: 500, quality: 85) {
...GatsbyImageSharpFluid_noBase64
}
}
}
}
}
}
}
`}
render={data => {
const projects = data.allProjectsYaml.edges
return (
<div
className={styles.item}
key={node.slug}
ref={node => {
if (current) this.currentItem = node
}}
>
<ProjectLink node={node} />
</div>
<FullWidth>
<nav
className={styles.projectNav}
ref={node => (this.scrollContainer = node)}
>
{projects.map(({ node }) => {
const current = node.slug === slug
return (
<div
className={styles.item}
key={node.slug}
ref={node => current && (this.currentItem = node)}
>
<ProjectLink node={node} />
</div>
)
})}
</nav>
</FullWidth>
)
})}
</nav>
}}
/>
)
}
}
@ -74,8 +109,7 @@ ProjectLink.propTypes = {
}
ProjectNav.propTypes = {
projects: PropTypes.array,
project: PropTypes.object
slug: PropTypes.string
}
export default ProjectNav

View File

@ -1,7 +1,7 @@
@import 'variables';
.projectNav {
composes: fullWidth from '../atoms/FullWidth.module.scss';
// composes: fullWidth from '../atoms/FullWidth.module.scss';
white-space: nowrap;
overflow-y: hidden;
overflow-x: scroll;

View File

@ -16,11 +16,12 @@
text-align: center;
background: rgba(#fff, .15);
border-radius: .25rem;
border: .05rem solid transparent;
color: $brand-grey-light;
margin-left: $spacer / 2;
margin-bottom: $spacer / 2;
flex: 0 0 calc(50% - #{$spacer / 2});
font-size: $font-size-base;
font-size: $font-size-small;
:global(.dark) & {
background: darken($body-background-color--dark, 1%);

View File

@ -1,68 +1,79 @@
import React, { PureComponent, Fragment } from 'react'
import PropTypes from 'prop-types'
import Helmet from 'react-helmet'
import { FadeIn } from '../atoms/Animations'
import { ReactComponent as Day } from '../../images/day.svg'
import { ReactComponent as Night } from '../../images/night.svg'
import Day from '../svg/Day'
import Night from '../svg/Night'
import styles from './ThemeSwitch.module.scss'
const ThemeToggle = props => {
return (
<span
id="toggle"
className={styles.checkboxContainer}
aria-live="assertive"
>
<Day className={props.dark ? null : 'active'} />
<span className={styles.checkboxFake} />
<Night className={props.dark ? 'active' : null} />
</span>
)
}
const ThemeToggle = props => (
<span id="toggle" className={styles.checkboxContainer} aria-live="assertive">
<Day className={props.dark ? null : 'active'} />
<span className={styles.checkboxFake} />
<Night className={props.dark ? 'active' : null} />
</span>
)
class ThemeSwitch extends PureComponent {
constructor(props) {
super(props)
this.state = { dark: false }
}
componentDidMount() {
const now = new Date().getHours()
if (now >= 19 || now <= 7) {
this.setState({ dark: true })
}
this.state = { dark: null }
}
isDark = () => this.state.dark === true
handleChange = () => {
this.setState({ dark: !this.isDark() })
darkLocalStorageMode = darkLocalStorage => {
if (darkLocalStorage === 'true') {
this.setState({ dark: true })
} else {
this.setState({ dark: false })
}
}
darkMode = now => {
if (!this.isDark() && (now >= 19 || now <= 7)) {
this.setState({ dark: true })
} else {
this.setState({ dark: null })
}
}
componentDidMount() {
const now = new Date().getHours()
const darkLocalStorage = localStorage.getItem('dark')
if (darkLocalStorage) {
this.darkLocalStorageMode(darkLocalStorage)
} else {
this.darkMode(now)
}
}
handleChange = event => {
this.setState({ dark: event.target.checked })
localStorage.setItem('dark', event.target.checked)
}
render() {
return (
<Fragment>
<Helmet>
<body className={this.state.dark ? 'dark' : null} />
<body className={this.isDark() ? 'dark' : null} />
</Helmet>
<FadeIn>
<aside className={styles.themeSwitch}>
<label className={styles.checkbox}>
<span className={styles.label}>Toggle Night Mode</span>
<input
onChange={this.handleChange}
type="checkbox"
name="toggle"
value="toggle"
aria-describedby="toggle"
checked={this.state.dark}
/>
<ThemeToggle dark={this.state.dark} />
</label>
</aside>
</FadeIn>
<aside className={styles.themeSwitch}>
<label className={styles.checkbox}>
<span className={styles.label}>Toggle Night Mode</span>
<input
onChange={this.handleChange}
type="checkbox"
name="toggle"
value="toggle"
aria-describedby="toggle"
checked={this.isDark()}
/>
<ThemeToggle dark={this.isDark()} />
</label>
</aside>
</Fragment>
)
}

View File

@ -1,5 +1,6 @@
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { StaticQuery, graphql } from 'gatsby'
import Vcard from '../atoms/Vcard'
import LogoUnit from '../atoms/LogoUnit'
import Networks from '../molecules/Networks'
@ -14,24 +15,43 @@ class Footer extends PureComponent {
render() {
const meta = this.props.meta
const pkg = this.props.pkg
return (
<footer className={styles.footer}>
<LogoUnit meta={meta} minimal />
<Networks meta={meta} minimal />
<StaticQuery
query={graphql`
query {
# the package.json file
portfolioJson {
name
homepage
repository
bugs
}
}
`}
render={data => {
const pkg = data.portfolioJson
<p className={styles.footer__actions}>
<Vcard meta={meta} />
<a href={meta.gpg}>PGP/GPG key</a>
<a href={pkg.bugs}>Found a bug?</a>
</p>
<p className={styles.footer__copyright}>
<small>
&copy; {this.state.year} {meta.title} &mdash; All Rights Reserved
</small>
</p>
</footer>
return (
<footer className={styles.footer}>
<LogoUnit meta={meta} minimal />
<Networks meta={meta} minimal />
<p className={styles.footer__actions}>
<Vcard meta={meta} />
<a href={meta.gpg}>PGP/GPG key</a>
<a href={pkg.bugs}>Found a bug?</a>
</p>
<p className={styles.footer__copyright}>
<small>
&copy; {this.state.year} {meta.title} &mdash; All Rights
Reserved
</small>
</p>
</footer>
)
}}
/>
)
}
}

View File

@ -38,5 +38,5 @@
}
.footer__copyright {
opacity: .6;
opacity: .7;
}

View File

@ -1,7 +1,6 @@
import React, { PureComponent } from 'react'
import Link from 'gatsby-link'
import { Link } from 'gatsby'
import PropTypes from 'prop-types'
import { FadeIn } from '../atoms/Animations'
import Networks from '../molecules/Networks'
import Availability from '../molecules/Availability'
import ThemeSwitch from '../molecules/ThemeSwitch'
@ -11,53 +10,34 @@ import styles from './Header.module.scss'
class Header extends PureComponent {
constructor(props) {
super(props)
this.state = { classes: 'header' }
}
componentDidMount() {
this.toggleClasses()
}
componentDidUpdate() {
this.toggleClasses()
}
toggleClasses = () => {
if (this.props.isHomepage) {
this.setState({ classes: styles.header })
} else {
this.setState({ classes: `${styles.header} ${styles.minimal}` })
}
}
render() {
const meta = this.props.meta
const isHomepage = this.props.isHomepage
const { isHomepage, meta } = this.props
return (
<header className={this.state.classes}>
<header
className={
isHomepage ? `${styles.header}` : `${styles.header} ${styles.minimal}`
}
>
<ThemeSwitch />
<FadeIn>
<Link className={styles.header__link} to={'/'}>
<LogoUnit meta={meta} minimal={!isHomepage} />
</Link>
</FadeIn>
<Link className={styles.header__link} to={'/'}>
<LogoUnit meta={meta} minimal={!isHomepage} />
</Link>
<Networks meta={meta} hide={!isHomepage} />
<Availability
meta={meta}
hide={!isHomepage && !meta.availability.status}
/>
<Availability hide={!isHomepage && !meta.availability.status} />
</header>
)
}
}
Header.propTypes = {
meta: PropTypes.object,
isHomepage: PropTypes.bool
isHomepage: PropTypes.bool,
meta: PropTypes.object
}
export default Header

View File

@ -1,118 +0,0 @@
import React, { Component, Fragment } from 'react'
import PropTypes from 'prop-types'
import withRouter from 'react-router-dom/withRouter'
import TransitionGroup from 'react-transition-group/TransitionGroup'
import Head from '../components/atoms/Head'
import Header from '../components/organisms/Header'
import Footer from '../components/organisms/Footer'
import { FadeIn } from '../components/atoms/Animations'
import './index.scss'
class TransitionHandler extends Component {
shouldComponentUpdate() {
return this.props.location.pathname === window.location.pathname
}
render() {
const { children } = this.props
return <div className="transition-container">{children}</div>
}
}
const Main = ({ children }) => <main className="screen">{children}</main>
const TemplateWrapper = ({ data, location, children }) => {
const meta = data.dataYaml
const pkg = data.portfolioJson
const isHomepage = location.pathname === '/'
return (
<Fragment>
<Head meta={meta} />
<Header meta={meta} isHomepage={isHomepage} />
<TransitionGroup component={Main} appear={true}>
<FadeIn
key={location.pathname}
timeout={{ enter: 200, exit: 150, appear: 200 }}
>
<TransitionHandler location={location}>
{children()}
</TransitionHandler>
</FadeIn>
</TransitionGroup>
<Footer meta={meta} pkg={pkg} />
</Fragment>
)
}
TransitionHandler.propTypes = {
children: PropTypes.any,
location: PropTypes.object.isRequired
}
Main.propTypes = {
children: PropTypes.any
}
TemplateWrapper.propTypes = {
children: PropTypes.func,
data: PropTypes.object.isRequired,
location: PropTypes.object.isRequired
}
export default withRouter(TemplateWrapper)
export const query = graphql`
query metaQuery {
# the data/meta.yml file
dataYaml {
title
tagline
description
url
email
avatar {
childImageSharp {
original: resize {
src
}
small: resize(width: 256) {
src
}
}
}
img {
childImageSharp {
resize(width: 980) {
src
}
}
}
social {
Email
Blog
Twitter
GitHub
Dribbble
}
availability {
status
available
unavailable
}
gpg
addressbook
typekitID
}
# the package.json file
portfolioJson {
name
homepage
repository
bugs
}
}
`

View File

@ -1,71 +0,0 @@
var camelCase = require('camel-case') // that's the only change
var Property = require('./property')
function set(object, key, value) {
if (Array.isArray(object[key])) {
object[key].push(value)
} else if (object[key] != null) {
object[key] = [object[key], value]
} else {
object[key] = value
}
}
function createParams(params, param) {
var parts = param.split('=')
var k = camelCase(parts[0])
var value = parts[1]
if (value == null || value === '') {
value = parts[0]
k = 'type'
}
if (k === 'type') {
value
.toLowerCase()
.split(',')
.forEach(function(value) {
set(params, k, value)
})
return params
}
set(params, k, value)
return params
}
function parseLines(lines) {
var data = {}
// NOTE: Line format:
// PROPERTY[;PARAMETER[=VALUE]]:Attribute[;Attribute]
var line = null
var pattern = /^([^;:]+)((?:;(?:[^;:]+))*)(?:\:(.+))?$/i // eslint-disable-line no-useless-escape
var len = lines.length - 1
for (var i = 1; i < len; i++) {
line = lines[i]
var match = pattern.exec(line)
if (!match) continue
var name = match[1].split('.')
var property = name.pop()
var group = name.pop()
var value = match[3]
var params = match[2] ? match[2].replace(/^;|;$/g, '').split(';') : []
var propParams = params.reduce(createParams, group ? { group: group } : {})
var propName = camelCase(property)
var propVal = new Property(propName, value, propParams)
set(data, propName, propVal)
}
return data
}
module.exports = parseLines

View File

@ -1,146 +0,0 @@
/* eslint-disable no-unused-vars */
/**
* vCard Property
* @constructor
* @memberOf vCard
* @param {String} field
* @param {String} value
* @param {Object} params
* @return {Property}
*/
function Property(field, value, params) {
if (!(this instanceof Property)) return new Property(value)
if (params != null) Object.assign(this, params)
this._field = field
this._data = value
Object.defineProperty(this, '_field', { enumerable: false })
Object.defineProperty(this, '_data', { enumerable: false })
}
/**
* Constructs a vCard.Property from jCard data
* @param {Array} data
* @return {Property}
*/
Property.fromJSON = function(data) {
var field = data[0]
var params = data[1]
if (!/text/i.test(data[2])) params.value = data[2]
var value = Array.isArray(data[3]) ? data[3].join(';') : data[3]
return new Property(field, value, params)
}
/**
* Turn a string into capitalized dash-case
* @internal used by `Property#toString()`
* @param {String} value
* @return {String}
* @ignore
*/
function capitalDashCase(value) {
return value.replace(/([A-Z])/g, '-$1').toUpperCase()
}
/**
* Property prototype
* @type {Object}
*/
Property.prototype = {
constructor: Property,
/**
* Check whether the property is of a given type
* @param {String} type
* @return {Boolean}
*/
is: function(type) {
type = (type + '').toLowerCase()
return Array.isArray(this.type)
? this.type.indexOf(type)
: this.type === type
},
/**
* Check whether the property is empty
* @return {Boolean}
*/
isEmpty: function() {
return this._data == null && Object.keys(this).length === 0
},
/**
* Clone the property
* @return {Property}
*/
clone: function() {
return new Property(this._field, this._data, this)
},
/**
* Format the property as vcf with given version
* @param {String} version
* @return {String}
*/
toString: function(version) {
var propName =
(this.group ? this.group + '.' : '') + capitalDashCase(this._field)
var keys = Object.keys(this)
var params = []
for (var i = 0; i < keys.length; i++) {
if (keys[i] === 'group') continue
params.push(capitalDashCase(keys[i]) + '=' + this[keys[i]])
}
return (
propName +
(params.length ? ';' + params.join(';') : params) +
':' +
(Array.isArray(this._data) ? this._data.join(';') : this._data)
)
},
/**
* Get the property's value
* @return {String}
*/
valueOf: function() {
return this._data
},
/**
* Format the property as jCard data
* @return {Array}
*/
toJSON: function() {
var params = Object.assign({}, this)
if (params.value === 'text') {
params.value = void 0
delete params.value
}
var data = [this._field, params, this.value || 'text']
switch (this._field) {
default:
data.push(this._data)
break
case 'adr':
case 'n':
data.push(this._data.split(';'))
}
return data
}
}
// Exports
module.exports = Property

View File

@ -1,322 +0,0 @@
/* eslint-disable no-unused-vars */
/**
* vCard
* @constructor
* @return {vCard}
*/
function vCard() {
if (!(this instanceof vCard)) return new vCard()
/** @type {String} Version number */
this.version = vCard.versions[vCard.versions.length - 1]
/** @type {Object} Card data */
this.data = {}
}
/**
* vCard MIME type
* @type {String}
*/
vCard.mimeType = 'text/vcard'
/**
* vCard file extension
* @type {String}
*/
vCard.extension = '.vcf'
/**
* vCard versions
* @type {Array}
*/
vCard.versions = ['2.1', '3.0', '4.0']
/**
* Folds a long line according to the RFC 5322.
* @see http://tools.ietf.org/html/rfc5322#section-2.1.1
* @param {String} input
* @param {Number} maxLength
* @param {Boolean} hardWrap
* @return {String}
*/
vCard.foldLine = require('foldline')
/**
* Normalizes input (cast to string, line folding, whitespace)
* @param {String} input
* @return {String}
*/
vCard.normalize = function(input) {
return (
(input + '')
// Trim whitespace
.replace(/^[\s\r\n]+|[\s\r\n]+$/g, '')
// Trim blank lines
.replace(/(\r?\n)\s*(\r?\n)|$/g, '$1')
// Unfold folded lines
.replace(/\r?\n[\x20\x09]+/g, '') // eslint-disable-line no-control-regex
)
}
/**
* Check whether a given version is supported
* @param {String} version
* @return {Boolean}
*/
vCard.isSupported = function(version) {
return /^\d\.\d$/.test(version) && vCard.versions.indexOf(version) !== -1
}
/**
* Parses a string or buffer into a vCard object
* @param {String|Buffer} value
* @return {Array<vCard>}
*/
vCard.parse = function(value) {
var objects = (value + '').split(/(?=BEGIN\:VCARD)/gi) // eslint-disable-line no-useless-escape
var cards = []
for (var i = 0; i < objects.length; i++) {
cards.push(new vCard().parse(objects[i]))
}
return cards
}
/**
* Parse an array of vcf formatted lines
* @internal used by `vCard#parse()`
* @type {Function}
*/
vCard.parseLines = require('./parse-lines')
/**
* Constructs a vCard from jCard data
* @param {Array} jcard
* @return {vCard}
*/
vCard.fromJSON = function(jcard) {
jcard = typeof jcard === 'string' ? JSON.parse(jcard) : jcard
if (jcard == null || !Array.isArray(jcard)) return new vCard()
if (!/vcard/i.test(jcard[0])) throw new Error('Object not in jCard format')
var card = new vCard()
jcard[1].forEach(function(prop) {
card.addProperty(vCard.Property.fromJSON(prop))
})
return card
}
/**
* Format a card object according to the given version
* @param {vCard} card
* @param {String} version
* @return {String}
*/
vCard.format = function(card, version) {
version = version || card.version || vCard.versions[vCard.versions.length - 1]
if (!vCard.isSupported(version))
throw new Error('Unsupported vCard version "' + version + '"')
var vcf = []
vcf.push('BEGIN:VCARD')
vcf.push('VERSION:' + version)
var props = Object.keys(card.data)
var prop = ''
for (var i = 0; i < props.length; i++) {
if (props[i] === 'version') continue
prop = card.data[props[i]]
if (Array.isArray(prop)) {
for (var k = 0; k < prop.length; k++) {
if (prop[k].isEmpty()) continue
vcf.push(vCard.foldLine(prop[k].toString(version), 75))
}
} else if (!prop.isEmpty()) {
vcf.push(vCard.foldLine(prop.toString(version), 75))
}
}
vcf.push('END:VCARD')
return vcf.join('\n')
}
// vCard Property constructor
vCard.Property = require('./property')
/**
* vCard prototype
* @type {Object}
*/
vCard.prototype = {
constructor: vCard,
/**
* Get a vCard property
* @param {String} key
* @return {Object|Array}
*/
get: function(key) {
if (this.data[key] == null) {
return this.data[key]
}
if (Array.isArray(this.data[key])) {
return this.data[key].map(function(prop) {
return prop.clone()
})
} else {
return this.data[key].clone()
}
},
/**
* Set a vCard property
* @param {String} key
* @param {String} value
* @param {Object} params
*/
set: function(key, value, params) {
return this.setProperty(new vCard.Property(key, value, params))
},
/**
* Add a vCard property
* @param {String} key
* @param {String} value
* @param {Object} params
*/
add: function(key, value, params) {
var prop = new vCard.Property(key, value, params)
this.addProperty(prop)
return this
},
/**
* Set a vCard property from an already
* constructed vCard.Property
* @param {vCard.Property} prop
*/
setProperty: function(prop) {
this.data[prop._field] = prop
return this
},
/**
* Add a vCard property from an already
* constructed vCard.Property
* @param {vCard.Property} prop
*/
addProperty: function(prop) {
var key = prop._field
if (Array.isArray(this.data[key])) {
this.data[key].push(prop)
} else if (this.data[key] != null) {
this.data[key] = [this.data[key], prop]
} else {
this.data[key] = prop
}
return this
},
/**
* Parse a vcf formatted vCard
* @param {String} value
* @return {vCard}
*/
parse: function(value) {
// Normalize & split
var lines = vCard.normalize(value).split(/\r?\n/g)
// Keep begin and end markers
// for eventual error messages
var begin = lines[0]
var version = lines[1]
var end = lines[lines.length - 1]
if (!/BEGIN:VCARD/i.test(begin))
throw new SyntaxError(
'Invalid vCard: Expected "BEGIN:VCARD" but found "' + begin + '"'
)
if (!/END:VCARD/i.test(end))
throw new SyntaxError(
'Invalid vCard: Expected "END:VCARD" but found "' + end + '"'
)
// TODO: For version 2.1, the VERSION can be anywhere between BEGIN & END
if (!/VERSION:\d\.\d/i.test(version))
throw new SyntaxError(
'Invalid vCard: Expected "VERSION:\\d.\\d" but found "' + version + '"'
)
this.version = version.substring(8, 11)
if (!vCard.isSupported(this.version))
throw new Error('Unsupported version "' + this.version + '"')
this.data = vCard.parseLines(lines)
return this
},
/**
* Format the vCard as vcf with given version
* @param {String} version
* @param {String} charset
* @return {String}
*/
toString: function(version, charset) {
version = version || this.version
return vCard.format(this, version)
},
/**
* Format the card as jCard
* @param {String} version='4.0'
* @return {Array} jCard
*/
toJCard: function(version) {
version = version || '4.0'
var keys = Object.keys(this.data)
var data = [['version', {}, 'text', version]]
var prop = null
for (var i = 0; i < keys.length; i++) {
if (keys[i] === 'version') continue
prop = this.data[keys[i]]
if (Array.isArray(prop)) {
for (var k = 0; k < prop.length; k++) {
data.push(prop[k].toJSON())
}
} else {
data.push(prop.toJSON())
}
}
return ['vcard', data]
},
/**
* Format the card as jCard
* @return {Array} jCard
*/
toJSON: function() {
return this.toJCard(this.version)
}
}
// Exports
module.exports = vCard

View File

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

View File

@ -1,39 +1,43 @@
import React from 'react'
import PropTypes from 'prop-types'
import Link from 'gatsby-link'
import { Link, graphql } from 'gatsby'
import Layout from '../components/Layout'
import ProjectImage from '../components/atoms/ProjectImage'
import FullWidth from '../components/atoms/FullWidth'
import styles from './index.module.scss'
const Home = ({ data }) => {
const Home = ({ data, location }) => {
const projects = data.allProjectsYaml.edges
return (
<FullWidth className="projects">
{projects.map(({ node }) => {
const { slug, title, img } = node
<Layout location={location}>
<FullWidth className="projects">
{projects.map(({ node }) => {
const { slug, title, img } = node
return (
<article className={styles.project} key={slug}>
<Link to={slug}>
<h1 className={styles.title}>{title}</h1>
<ProjectImage sizes={img.childImageSharp.sizes} alt={title} />
</Link>
</article>
)
})}
</FullWidth>
return (
<article className={styles.project} key={slug}>
<Link to={slug}>
<h1 className={styles.title}>{title}</h1>
<ProjectImage fluid={img.childImageSharp.fluid} alt={title} />
</Link>
</article>
)
})}
</FullWidth>
</Layout>
)
}
Home.propTypes = {
data: PropTypes.object
data: PropTypes.object,
location: PropTypes.object
}
export default Home
export const IndexQuery = graphql`
query IndexQuery {
query {
allProjectsYaml {
edges {
node {
@ -41,7 +45,7 @@ export const IndexQuery = graphql`
slug
img {
childImageSharp {
...ProjectImageSizes
...ProjectImageFluid
}
}
}

View File

@ -10,20 +10,14 @@ html,
body {
margin: 0;
padding: 0;
height: 100%;
}
html {
font-size: $font-size-root;
&.wf-loading,
&.wf-inactive {
font-size: $font-size-root - 2px;
}
background: $body-background-color;
}
body {
background: $body-background-color;
font-family: $font-family-base;
font-weight: $font-weight-base;
font-size: $font-size-base;
@ -33,6 +27,14 @@ body {
font-feature-settings: 'liga', 'kern';
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
min-height: 100vh;
background: $body-background-color;
transition: background .6s $easing;
&.dark {
background-color: $body-background-color--dark;
color: $text-color--dark;
}
}
p,
@ -115,25 +117,4 @@ svg {
display: flex;
min-height: 100vh;
flex-direction: column;
background: $body-background-color;
transition: background .6s $easing;
.dark & {
background: $body-background-color--dark;
color: $text-color--dark;
}
}
.screen {
flex: 1;
padding: $spacer;
}
.transition-group {
position: relative;
}
.transition-container {
position: relative;
width: 100%;
}

View File

@ -1,7 +1,9 @@
import React, { Component, Fragment } from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import Helmet from 'react-helmet'
import ReactMarkdown from 'react-markdown'
import { graphql } from 'gatsby'
import Layout from '../components/Layout'
import Content from '../components/atoms/Content'
import FullWidth from '../components/atoms/FullWidth'
import ProjectImage from '../components/atoms/ProjectImage'
@ -9,67 +11,55 @@ import ProjectTechstack from '../components/molecules/ProjectTechstack'
import ProjectLinks from '../components/molecules/ProjectLinks'
import ProjectNav from '../components/molecules/ProjectNav'
import SEO from '../components/atoms/SEO'
import './Project.scss'
const ProjectMeta = props => {
const { links, techstack } = props
import styles from './Project.module.scss'
return (
<footer className="project__meta">
{!!links && <ProjectLinks links={links} />}
{!!techstack && <ProjectTechstack techstack={techstack} />}
</footer>
)
}
const ProjectMeta = ({ links, techstack }) => (
<footer className={styles.project__meta}>
{!!links && <ProjectLinks links={links} />}
{!!techstack && <ProjectTechstack techstack={techstack} />}
</footer>
)
const ProjectImages = props => (
const ProjectImages = ({ projectImages, title }) => (
<FullWidth>
{props.projectImages.map(({ node }) => (
<ProjectImage key={node.id} sizes={node.sizes} alt={props.title} />
{projectImages.map(({ node }) => (
<div className={styles.spacer} key={node.id}>
<ProjectImage fluid={node.fluid} alt={title} />
</div>
))}
</FullWidth>
)
class Project extends Component {
constructor(props) {
super(props)
const Project = ({ data, location }) => {
const meta = data.dataYaml
const project = data.projectsYaml
const projectImages = data.projectImages.edges
const { title, links, techstack } = project
const description = data.projectsYaml.description
const descriptionWithLineBreaks = description.split('\n').join('\n\n')
const description = this.props.data.projectsYaml.description
return (
<Layout location={location}>
<Helmet title={title} />
this.state = {
descriptionWithLineBreaks: description.split('\n').join('\n\n')
}
}
<SEO project={project} meta={meta} />
render() {
const meta = this.props.data.dataYaml
const projects = this.props.data.allProjectsYaml.edges
const project = this.props.data.projectsYaml
const projectImages = this.props.data.projectImages.edges
const { title, links, techstack } = project
<article className={styles.project}>
<Content>
<h1 className={styles.project__title}>{title}</h1>
<ReactMarkdown
source={descriptionWithLineBreaks}
className={styles.project__description}
/>
<ProjectImages projectImages={projectImages} title={title} />
<ProjectMeta links={links} techstack={techstack} />
</Content>
</article>
return (
<Fragment>
<Helmet title={title} />
<SEO project={project} meta={meta} />
<article className="project">
<Content>
<h1 className="project__title">{title}</h1>
<ReactMarkdown
source={this.state.descriptionWithLineBreaks}
className="project__description"
/>
<ProjectImages projectImages={projectImages} title={title} />
<ProjectMeta links={links} techstack={techstack} />
</Content>
</article>
<ProjectNav projects={projects} project={project} />
</Fragment>
)
}
<ProjectNav slug={project.slug} />
</Layout>
)
}
ProjectMeta.propTypes = {
@ -84,13 +74,13 @@ ProjectImages.propTypes = {
Project.propTypes = {
data: PropTypes.object.isRequired,
pathContext: PropTypes.object.isRequired
location: PropTypes.object.isRequired
}
export default Project
export const projectAndProjectsQuery = graphql`
query ProjectBySlug($slug: String!) {
query($slug: String!) {
projectsYaml(slug: { eq: $slug }) {
title
slug
@ -122,11 +112,6 @@ export const projectAndProjectsQuery = graphql`
GitHub
Dribbble
}
availability {
status
available
unavailable
}
img {
childImageSharp {
resize(width: 980) {
@ -137,29 +122,13 @@ export const projectAndProjectsQuery = graphql`
}
projectImages: allImageSharp(
filter: { id: { regex: $slug } }
sort: { fields: [id], order: ASC }
filter: { fluid: { originalName: { regex: $slug } } }
sort: { fields: [fluid___originalName], order: ASC }
) {
edges {
node {
id
...ProjectImageSizes
}
}
}
allProjectsYaml {
edges {
node {
title
slug
img {
childImageSharp {
sizes(maxWidth: 500, quality: 85) {
...GatsbyImageSharpSizes_noBase64
}
}
}
...ProjectImageFluid
}
}
}

View File

@ -1,12 +1,13 @@
@import 'variables';
@import '../components/atoms/ProjectImage.module';
.project {
.project__image-wrap {
margin-bottom: $spacer * 3;
// .project__imagewrap {
.spacer {
//composes: project__imagewrap from '../components/atoms/ProjectImage.module';
margin-bottom: $spacer * 3;
@media (min-width: 30rem) {
margin-bottom: $spacer * 6;
}
@media (min-width: 30rem) {
margin-bottom: $spacer * 6;
}
}