To Vanilla!

This commit is contained in:
Eric Jinks 2018-01-23 12:21:56 +10:00
parent 9987db1956
commit 05e7c2babd
37 changed files with 610 additions and 545 deletions

View file

@ -16,6 +16,7 @@
"glob": "^7.1.2", "glob": "^7.1.2",
"gray-matter": "^3.1.1", "gray-matter": "^3.1.1",
"js-yaml": "^3.10.0", "js-yaml": "^3.10.0",
"prop-types": "^15.6.0",
"react-scripts": "^1.0.10", "react-scripts": "^1.0.10",
"sharp": "^0.18.4", "sharp": "^0.18.4",
"snazzy": "^7.0.0", "snazzy": "^7.0.0",
@ -23,35 +24,33 @@
"sw-precache": "^5.2.0" "sw-precache": "^5.2.0"
}, },
"dependencies": { "dependencies": {
"@researchgate/react-intersection-observer": "^0.5.0", "@researchgate/react-intersection-observer": "^0.6.0",
"core-js": "^2.5.3", "core-js": "^2.5.3",
"intersection-observer": "^0.5.0", "intersection-observer": "^0.5.0",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"netlify-identity-widget": "^1.2.0", "netlify-identity-widget": "^1.2.0",
"normalize.css": "^7.0.0", "normalize.css": "^7.0.0",
"polished": "^1.7.0",
"react": "^16.0.0", "react": "^16.0.0",
"react-dom": "^16.0.0", "react-dom": "^16.0.0",
"react-helmet": "^5.1.3", "react-helmet": "^5.1.3",
"react-markdown": "^2.5.0", "react-markdown": "^2.5.0",
"react-router-dom": "^4.1.1", "react-router-dom": "^4.1.1",
"react-snapshot": "^1.1.0", "react-snapshot": "^1.1.0"
"styled-components": "^2.1.0"
}, },
"scripts": { "scripts": {
"start": "npm run watch:content & react-scripts start", "start": "npm run watch:content && react-scripts start",
"build": "npm run prepare-content && react-scripts build && react-snapshot && npm run sw", "build":
"npm run prepare-content && react-scripts build && react-snapshot && npm run sw",
"parse-content": "node ./functions/parse-content.js", "parse-content": "node ./functions/parse-content.js",
"resize-images": "node ./functions/resize-images.js", "resize-images": "node ./functions/resize-images.js",
"prepare-content": "npm run parse-content & npm run resize-images", "prepare-content": "npm run parse-content && npm run resize-images",
"watch:content": "chokidar 'content/**/**' -c 'npm run prepare-content' --initial", "watch:content":
"chokidar 'content/**/**' -c 'npm run prepare-content' --initial",
"sw": "sw-precache --config='sw-precache-config.js'", "sw": "sw-precache --config='sw-precache-config.js'",
"test": "standard | snazzy && react-scripts test --env=jsdom", "test": "standard | snazzy && react-scripts test --env=jsdom",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"reactSnapshot": { "reactSnapshot": {
"include": [ "include": ["/404"]
"/404"
]
} }
} }

BIN
public/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -14,7 +14,7 @@
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>Netlify CMS + React Starter</title> <title>Netlify CMS + React Starter</title>
<!-- Place Google Fonts css here -->
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

View file

@ -0,0 +1,18 @@
.BackgroundImage {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-position: center;
transition: opacity 0.5s ease;
overflow: hidden;
}
.BackgroundImage-lazy {
filter: blur(5px);
}
.BackgroundImage-lazy-loaded {
filter: blur(0);
}

View file

@ -0,0 +1,74 @@
import React from 'react'
import PropTypes from 'prop-types'
import 'intersection-observer'
import Observer from '@researchgate/react-intersection-observer'
import './BackgroundImage.css'
import { getImageSrc } from '../util/getImageUrl'
export default class BackgroundImage extends React.Component {
static defaultProps = {
lazy: false,
src: ''
}
state = {
src:
this.props.src.indexOf('http') === 0
? ''
: getImageSrc(this.props.src, this.props.lazy ? 10 : 1800),
dataSrc: getImageSrc(this.props.src, 1800),
loaded: false
}
handleIntersection = e => {
if (e.isIntersecting) {
const img = new Image()
img.src = this.state.dataSrc
img.onload = () => {
this.setState({
src: this.state.dataSrc,
loaded: true
})
}
}
}
componentWillReceiveProps (nextProps) {
if (this.props.src === nextProps.src) return
this.setState({
src: getImageSrc(nextProps.src, nextProps.lazy ? 10 : 1800),
dataSrc: getImageSrc(nextProps.src, 1800)
})
}
render () {
let className = this.props.className || ''
if (this.state.loaded) className += ' BackgroundImage-lazy-loaded'
if (this.props.lazy) className += ' BackgroundImage-lazy'
const options = {
onChange: this.handleIntersection,
onlyOnce: true,
rootMargin: '0% 0% 100%'
}
return (
<Observer {...options}>
<div
className={`BackgroundImage absolute ${className || ''}`}
src={this.state.src}
style={{
backgroundImage: `url(${this.state.src})`,
backgroundSize: this.props.contain ? 'contain' : 'cover',
opacity: this.props.opacity || 1
}}
/>
</Observer>
)
}
}
BackgroundImage.propTypes = {
src: PropTypes.string.isRequired
}

View file

@ -0,0 +1,8 @@
.Content {
white-space: pre-line;
}
.Content--Image {
max-width: 100%;
height: auto;
}

View file

@ -1,10 +1,12 @@
import React from 'react' import React from 'react'
import styled from 'styled-components'
import Marked from 'react-markdown' import Marked from 'react-markdown'
import { getImageSrc, getImageSrcset } from '../util/getImageUrl' import { getImageSrc, getImageSrcset } from '../util/getImageUrl'
import './Content.css'
export default ({ source }) => ( export default ({ source }) => (
<Content <Marked
className='Content'
source={source} source={source}
renderers={{ renderers={{
Image: ImageWithSrcset Image: ImageWithSrcset
@ -12,18 +14,12 @@ export default ({ source }) => (
/> />
) )
const Image = styled.img` const ImageWithSrcset = ({ nodeKey, src, alt, ...props }) => (
max-width: 100%; <img
height: auto; className='Content--Image'
`
const Content = styled(Marked)`
white-space: pre-line;
`
const ImageWithSrcset = props => (
<Image
{...props} {...props}
src={getImageSrc(props.src)} src={getImageSrc(src)}
srcSet={getImageSrcset(props.src)} srcSet={getImageSrcset(src)}
alt={alt}
/> />
) )

View file

@ -0,0 +1,60 @@
.EnquiryForm {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
max-width: 500px;
margin: 2.5rem 0;
}
.EnquiryForm > * + * {
margin-top: 1.5rem;
}
.EnquiryForm--Label {
width: 100%;
display: flex;
align-items: center;
flex-wrap: wrap;
}
.EnquiryForm--Input,
.EnquiryForm--Textarea {
font-family: inherit;
font-weight: 400;
flex-grow: 1;
box-sizing: border-box;
display: block;
margin: 0;
border: none;
padding: 1em;
line-height: 1;
transition: border-color 0.2s;
resize: none;
background: var(--lightGrey);
border-radius: 0;
}
.EnquiryForm--Input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px var(--lightGrey) inset !important;
}
.EnquiryForm--Input:focus {
outline: none;
border-color: black;
}
.EnquiryForm--Input[disabled] {
opacity: 0.4;
pointer-events: none;
cursor: progress;
}
.EnquiryForm--Input::placeholder {
text-transform: uppercase;
}
.EnquiryForm--SubmitButton {
width: 90%;
margin: 2rem auto 0;
}

View file

@ -0,0 +1,62 @@
import React from 'react'
import './EnquiryFormSimple.css'
export default ({
name = 'Simple Form',
subject = '', // optional subject of the notification email
action = ''
}) => (
<form
className='EnquiryForm'
name={name}
action={action}
data-netlify=''
data-netlify-honeypot='_gotcha'
>
<label className='EnquiryForm--Label'>
<input
className='EnquiryForm--Input'
type='text'
placeholder='Name'
name='name'
required
/>
</label>
<label className='EnquiryForm--Label'>
<input
className='EnquiryForm--Input'
type='phone'
placeholder='Phone'
name='phone'
required
/>
</label>
<label className='EnquiryForm--Label'>
<input
className='EnquiryForm--Input'
type='email'
placeholder='Email'
name='email'
required
/>
</label>
<label className='EnquiryForm--Label'>
<textarea
className='EnquiryForm--Textarea'
placeholder='Message'
name='message'
rows='10'
required
/>
</label>
<input type='text' name='_gotcha' style={{ display: 'none' }} />
{!!subject && <input type='hidden' name='subject' value={subject} />}
<input type='hidden' name='form-name' value={name} />
<input
className='Button Button--tertiary EnquiryForm--SubmitButton'
type='submit'
value='Enquire'
/>
</form>
)

View file

@ -0,0 +1,48 @@
.GithubCorner {
position: fixed;
top: 0;
right: 0;
z-index: 2;
}
.GithubCorner svg {
fill: #151513;
color: #fff;
border: 0;
max-width: 12.5vmin;
max-height: 12.5vmin;
}
.GithubCorner .octo-arm {
transform-origin: 130px 106px;
}
.GithubCorner:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
@media (max-width: 500px) {
.GithubCorner:hover .octo-arm {
animation: none;
}
.GithubCorner .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
}
@keyframes octocat-wave {
0%,
100% {
transform: rotate(0);
}
20%,
60% {
transform: rotate(-25deg);
}
40%,
80% {
transform: rotate(10deg);
}
}

View file

@ -1,59 +1,20 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import './GithubCorner.css'
const GithubCorner = styled.a`
position: fixed;
top: 0;
right: 0;
z-index: 2;
svg {
fill: #151513;
color: #fff;
border: 0;
max-width: 12.5vmin;
max-height: 12.5vmin;
}
.octo-arm {
transform-origin: 130px 106px;
}
&:hover .octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
@keyframes octocat-wave {
0%, 100% {
transform: rotate(0);
}
20%, 60% {
transform: rotate(-25deg);
}
40%, 80% {
transform: rotate(10deg)
}
}
@media (max-width:500px) {
&:hover .octo-arm {
animation: none;
}
.octo-arm {
animation: octocat-wave 560ms ease-in-out;
}
}
`
export default ({ url }) => ( export default ({ url }) => (
<GithubCorner href={url} aria-label='View source on Github'> <a className='GithubCorner' href={url} aria-label='View source on Github'>
<svg width='80' height='80' viewBox='0 0 250 250' aria-hidden='true'> <svg width='80' height='80' viewBox='0 0 250 250' aria-hidden='true'>
<path d='M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z' /> <path d='M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z' />
<path className='octo-arm' d='M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2' fill='currentColor' /> <path
<path className='octo-body' d='M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z' fill='currentColor' /> className='octo-arm'
d='M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2'
fill='currentColor'
/>
<path
className='octo-body'
d='M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z'
fill='currentColor'
/>
</svg> </svg>
</GithubCorner> </a>
) )

View file

@ -0,0 +1,18 @@
.LazyEmbed {
position: relative;
padding-bottom: 56.25%;
height: 0;
overflow: hidden;
max-width: 100%;
height: auto;
}
.LazyEmbed iframe,
.LazyEmbed object,
.LazyEmbed embed {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}

View file

@ -0,0 +1,55 @@
import React from 'react'
import 'intersection-observer'
import Observer from '@researchgate/react-intersection-observer'
import './LazyEmbed.css'
class LazyEmbed extends React.Component {
static defaultProps = {
lazy: true
}
state = {
src: null,
dataSrc: this.props.src,
loaded: !this.props.lazy
}
handleIntersection = e => {
if (e.isIntersecting) {
this.setState({
src: this.state.dataSrc,
loaded: true
})
}
}
componentWillReceiveProps (nextProps) {
if (this.props.src === nextProps.src) return
this.setState({
src: nextProps.src,
dataSrc: nextProps.src
})
}
render () {
let className = this.props.className || ''
if (this.state.loaded) className += ' loaded'
const options = {
onChange: this.handleIntersection,
onlyOnce: true,
rootMargin: '0% 0% 100%'
}
return (
<Observer {...options}>
<div
className={`LazyEmbed ${className}`}
dangerouslySetInnerHTML={{ __html: this.state.src }}
/>
</Observer>
)
}
}
export default LazyEmbed

View file

@ -0,0 +1,14 @@
.LazyImage {
display: block;
width: 100%;
margin-bottom: 2.5rem;
filter: blur(5px);
}
.LazyImage.loaded {
filter: blur(0);
}
.LazyImage:last-child {
margin-bottom: 0;
}

View file

@ -1,53 +1,85 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import styled from 'styled-components'
import { getImageSrc, getImageSrcset } from '../util/getImageUrl'
import 'intersection-observer' import 'intersection-observer'
import Observer from '@researchgate/react-intersection-observer' import Observer from '@researchgate/react-intersection-observer'
const Image = styled.img` import { getImageSrc, getImageSrcset } from '../util/getImageUrl'
display: block; import './LazyImage.css'
width: 100%;
margin-bottom: 2.5rem;
&:last-child {
margin-bottom: 0;
}
`
class LazyImage extends React.Component { class LazyImage extends React.Component {
static defaultProps = {
lazy: false,
enableSrcset: true,
imageSize: '300'
}
state = { state = {
src: getImageSrc(this.props.src, '10'), src: getImageSrc(
srcSet: '', this.props.src,
dataSrc: getImageSrc(this.props.src, '300'), this.props.lazy ? '10' : this.props.imageSize
dataSrcSet: getImageSrcset(this.props.src) ),
srcSet: this.props.lazy ? '' : getImageSrcset(this.props.src),
dataSrc: getImageSrc(this.props.src, this.props.imageSize),
dataSrcSet: getImageSrcset(this.props.src),
loaded: !this.props.lazy
} }
handleIntersection = e => { handleIntersection = e => {
if (e.isIntersecting) { if (e.isIntersecting) {
this.setState({ const img = new Image()
src: this.state.dataSrc, img.src = this.state.dataSrc
srcSet: this.state.dataSrcSet img.onload = () => {
}) this.setState({
src: this.state.dataSrc,
srcSet: this.state.dataSrcSet,
loaded: true
})
}
} }
} }
componentWillReceiveProps (nextProps) {
if (this.props.src === nextProps.src) return
this.setState({
src: getImageSrc(nextProps.src, '10'),
srcSet: '',
dataSrc: getImageSrc(nextProps.src, '300'),
dataSrcSet: getImageSrcset(nextProps.src)
})
}
render () { render () {
const { sizes, alt, className } = this.props const { sizes, lazy, onClick, alt } = this.props
let className = this.props.className || ''
if (this.state.loaded) className += ' loaded'
const options = { const options = {
onChange: this.handleIntersection, onChange: this.handleIntersection,
onlyOnce: true, onlyOnce: true,
rootMargin: '0% 0% 50%' rootMargin: '0% 0% 100%'
}
if (!lazy) {
return (
<img
className={`LazyImage ${className}`}
src={this.state.src}
srcSet={this.props.enableSrcset ? this.state.srcSet : undefined}
sizes={sizes || '100vw'}
onClick={onClick}
alt={alt}
/>
)
} }
return ( return (
<Observer {...options}> <Observer {...options}>
<Image <img
className={`LazyImage ${className}`}
src={this.state.src} src={this.state.src}
srcSet={this.state.srcSet} srcSet={this.props.enableSrcset ? this.state.srcSet : undefined}
sizes={sizes || '100vw'} sizes={sizes || '100vw'}
onClick={onClick}
alt={alt} alt={alt}
className={className}
/> />
</Observer> </Observer>
) )

9
src/components/Logo.css Normal file
View file

@ -0,0 +1,9 @@
.Logo {
display: block;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
height: 2rem;
width: 2.5rem;
margin-right: 1rem;
}

View file

@ -1,7 +1,12 @@
import styled from 'styled-components' import React from 'react'
export default styled.span` import './Logo.css'
display: block;
font-size: 2rem; export default ({ inverted }) => (
padding: 0 1rem; <div
` className='Logo'
style={{
backgroundImage: 'url(/images/logo.png)'
}}
/>
)

6
src/components/Nav.css Normal file
View file

@ -0,0 +1,6 @@
.Nav {
background: white;
position: sticky;
top: 0;
z-index: 1;
}

View file

@ -1,27 +1,27 @@
import React from 'react' import React from 'react'
import styled from 'styled-components' import { Link } from 'react-router-dom'
import { Container, Flex } from './common'
import NavLink from './NavLink'
import Logo from './Logo' import Logo from './Logo'
import NavLink from './NavLink'
import './Nav.css'
const Nav = styled.div` export default ({ handlePopupOpen }) => (
background: white; <nav className='Nav'>
position: sticky; <div className='Container'>
top: 0; <div className='Flex alignCenter justifyStart'>
z-index: 1; <Link to='/'>
` <Logo />
</Link>
export default (props) => ( <NavLink to='/' exact>
<Nav> Home
<Container> </NavLink>
<Flex alignCenter> <NavLink to='/about/' exact>
<Logo> About
<span role='img' aria-label='Chili'>🌶</span> </NavLink>
</Logo> <NavLink to='/contact/' exact>
<NavLink to='/' exact>Home</NavLink> Contact
<NavLink to='/about/' exact>About</NavLink> </NavLink>
<NavLink to='/contact/' exact>Contact</NavLink> </div>
</Flex> </div>
</Container> </nav>
</Nav>
) )

View file

@ -0,0 +1,17 @@
.NavLink {
padding: 0.5rem 1rem;
display: block;
font-weight: 400;
transition: color 0.2s, border-bottom-color 0.2s;
color: inherit;
text-decoration: none;
border-bottom: 1px solid;
border-bottom-color: transparent;
}
.NavLink:hover,
.NavLink:active,
.NavLink:focus {
color: var(--primary);
border-bottom-color: currentColor;
}

View file

@ -1,29 +1,10 @@
import React from 'react' import React from 'react'
import { Route, Link } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import styled from 'styled-components'
import { color } from '../globalStyles' import './NavLink.css'
const NavLink = styled.span` export default ({ className, children, ...props }) => (
a { <NavLink {...props} className={`NavLink ${className || ''}`}>
padding: .5rem 1rem; {children}
display: block; </NavLink>
font-weight: 400;
transition: color 0.2s, border-bottom-color 0.2s;
color: ${props => props.active ? color.primary : 'inherit'};
text-decoration: none;
border-bottom: 1px solid;
border-bottom-color: ${props => props.active ? color.primary : 'transparent'};
&:hover, &:active, &:focus {
color: ${props => props.active ? color.primary : 'inherit'};
}
}
`
export default ({ to, exact, match, children }) => (
<Route path={to} exact={exact} children={({match}) => (
<NavLink active={match}>
<Link to={to}>{children}</Link>
</NavLink>
)} />
) )

View file

@ -1,119 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import { color } from '../globalStyles'
export default ({
name = 'Simple Form',
subject = '', // optional subject of the notification email
action = ''
}) => (
<StyledForm
name={name}
action={action}
data-netlify=''
data-netlify-honeypot='_gotcha'
>
<Label className='Label'>
<Input
className='Input'
type='text'
placeholder='Your Name'
name='name'
required
/>
</Label>
<Label className='Label'>
<Input
className='Input'
type='email'
placeholder='Your Email'
name='email'
required
/>
</Label>
<Label className='Label'>
<Textarea
className='Input'
placeholder='Message'
name='message'
rows='10'
required
/>
</Label>
<Input type='text' name='_gotcha' style={{ display: 'none' }} />
{!!subject && <Input type='hidden' name='subject' value={subject} />}
<Input type='hidden' name='form-name' value={name} />
<Button className='button' type='submit' value='Send' />
</StyledForm>
)
const StyledForm = styled.form`
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: flex-start;
max-width: 500px;
margin: 2.5rem 0;
& > * + * {
margin-top: 1.5rem;
}
`
const Label = styled.label`
width: 100%;
display: flex;
align-items: center;
flex-wrap: wrap;
`
const Input = styled.input`
font-family: inherit;
font-weight: 400;
flex-grow: 1;
box-sizing: border-box;
display: block;
margin: 0;
border: none;
border-bottom: 1px solid #bababa;
padding: 0.5em 0;
line-height: 1;
transition: border-color 0.2s;
resize: none;
&:focus {
outline: none;
border-color: black;
}
&[disabled] {
opacity: 0.4;
pointer-events: none;
cursor: progress;
}
`
const Textarea = Input.withComponent('textarea')
const Button = styled.input`
background: ${color.primary};
color: white;
text-transform: uppercase;
text-decoration: none;
font-weight: 400;
letter-spacing: 0.15em;
font-size: 1.4rem;
display: inline-block;
padding: 1rem 2rem;
border: none;
transition: all 0.2s ease;
cursor: pointer;
&:hover,
&:focus {
color: white;
}
&[disabled] {
opacity: 0.4;
pointer-events: none;
cursor: progress;
}
`

View file

@ -1,7 +0,0 @@
import styled from 'styled-components'
export default styled.div`
p {
margin-bottom: 1rem;
}
`

View file

@ -0,0 +1,13 @@
.PageHeader {
line-height: 1em;
color: var(--primary);
background: var(--primary);
color: white;
}
.PageHeader--Title {
}
.PageHeader--Subtitle {
font-weight: 200;
}

View file

@ -1,35 +1,17 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import styled from 'styled-components'
import { Section, Container, BackgroundImage } from './common' import BackgroundImage from './BackgroundImage'
import { getImageSrc } from '../util/getImageUrl' import './PageHeader.css'
import { color } from '../globalStyles'
const Header = styled(Section)`
line-height: 1em;
color: ${color.primary};
background: ${color.primary};
color: white;
h2 {
font-weight: 200;
}
`
const PageHeader = ({ title, subtitle, backgroundImage }) => ( const PageHeader = ({ title, subtitle, backgroundImage }) => (
<Header relative> <div className='Section PageHeader relative'>
{backgroundImage && ( {backgroundImage && <BackgroundImage src={backgroundImage} opacity={0.5} />}
<BackgroundImage <div className='Container relative'>
image={getImageSrc(backgroundImage, '1200')} <h1 className='PageHeader--Title'>{title}</h1>
opacity={0.5} {subtitle ? <h2 className='PageHeader--Subtitle'>{subtitle}</h2> : ''}
/> </div>
)} </div>
<Container relative>
<h1>{title}</h1>
{subtitle ? <h2>{subtitle}</h2> : ''}
</Container>
</Header>
) )
PageHeader.propTypes = { PageHeader.propTypes = {

View file

@ -1,40 +1,4 @@
import React, { Component } from 'react' import { Component } from 'react'
import styled from 'styled-components'
import { color } from '../globalStyles'
const Notification = styled.div`
position: fixed;
bottom: 0;
left: 0;
right: 0;
text-align: center;
background: ${color.primary};
color: white;
padding: 1rem;
min-height: 5rem;
transition: transform 0.1s ease-in-out;
transform: translateY(${props => (props.message ? '0' : '100%')});
`
const CloseButton = styled.button.attrs({
children: 'x'
})`
border: none;
background: white;
color: ${color.primary};
border-radius: 100%;
width: 2.5rem;
height: 2.5rem;
position: absolute;
right: 1rem;
top: 1rem;
cursor: pointer;
&:hover,
&:focus {
opacity: 0.7;
outline: none;
}
`
export default class ServiceWorkerNotifications extends Component { export default class ServiceWorkerNotifications extends Component {
static defaultProps = { static defaultProps = {
@ -93,11 +57,6 @@ export default class ServiceWorkerNotifications extends Component {
render () { render () {
this.props.reloadOnUpdate && this.reloadIfUpdated() this.props.reloadOnUpdate && this.reloadIfUpdated()
return this.state.message ? ( return null
<Notification message={this.state.message}>
{this.state.message}
<CloseButton onClick={this.handleDismiss} />
</Notification>
) : null
} }
} }

View file

@ -1,82 +0,0 @@
import styled, { css } from 'styled-components'
const mixin = {
position: css`
${props => {
if (props.relative) return 'position: relative;'
if (props.absolute) return 'position: absolute;'
}};
`,
textAlign: css`
${props => {
if (props.taCenter) return 'text-align: center;'
if (props.taLeft) return 'text-align: left;'
if (props.taRight) return 'text-align: right;'
}};
`
}
export const Absolute = styled.div`
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
`
export const Relative = styled.div`
position: relative;
z-index: 0;
`
export const Section = styled.section`
${mixin.position}
width: 100%;
padding: ${props => {
if (props.thick) return '10rem 0'
if (props.thin) return '2.5rem 0'
return '5rem 0'
}};
`
export const Container = styled.div`
${mixin.position}
${mixin.textAlign}
margin: 0 auto;
width: 90vw;
max-width: ${props => {
if (props.skinny) return '888px'
if (props.skinnier) return '555px'
return '1111px'
}};
`
export const Flex = styled.div`
display: flex;
flex-direction: ${props => props.column ? 'column' : 'row'};
justify-content: ${props => {
if (props.justifyCenter) return 'center'
if (props.justifyEnd) return 'flex-end'
if (props.justifyBetween) return 'space-between'
if (props.justifyAround) return 'space-around'
return 'flex-start'
}};
align-items: ${props => {
if (props.alignStart) return 'flex-start'
if (props.alignEnd) return 'flex-end'
if (props.alignStretch) return 'stretch'
return 'center'
}};
height: ${props => props.fill ? '100%' : 'auto'};
width: ${props => props.fill ? '100%' : 'auto'};
`
export const BackgroundImage = styled(Absolute)`
background-size: cover;
background-position: center;
background-image: url(${props => props.image});
opacity: ${props => props.opacity || 1};
transition: opacity .5s ease;
`

View file

@ -142,8 +142,8 @@ pre code:after {
} }
.Container { .Container {
max-width: 1467px; max-width: 1111px;
width: 90vw; width: 90%;
margin: 0 auto; margin: 0 auto;
} }
.Container.skinny { .Container.skinny {

2
src/views/About.css Normal file
View file

@ -0,0 +1,2 @@
.About {
}

View file

@ -1,14 +1,13 @@
import React from 'react' import React from 'react'
import Helmet from 'react-helmet' import Helmet from 'react-helmet'
import Page from '../components/Page'
import PageHeader from '../components/PageHeader' import PageHeader from '../components/PageHeader'
import LazyImage from '../components/LazyImage' import LazyImage from '../components/LazyImage'
import { Container, Section } from '../components/common'
import Content from '../components/Content.js' import Content from '../components/Content.js'
import './About.css'
export default ({ page }) => ( export default ({ page }) => (
<Page> <div className='About'>
<Helmet> <Helmet>
<title>{page.title}</title> <title>{page.title}</title>
</Helmet> </Helmet>
@ -17,17 +16,17 @@ export default ({ page }) => (
subtitle={page.subtitle} subtitle={page.subtitle}
backgroundImage={page.featuredImage} backgroundImage={page.featuredImage}
/> />
<Section thin> <div className='Section thin'>
<Container> <div className='Container'>
<Content source={page.section1} /> <Content source={page.section1} />
</Container> </div>
</Section> </div>
<Section thin> <div className='Seciton thin'>
<Container> <div className='Container'>
<Content source={page.section2} /> <Content source={page.section2} />
<p>The image below is a {'<LazyImage />'}</p> <p>The image below is a {'<LazyImage />'}</p>
<LazyImage src={page.featuredImage} alt='LazyImage' /> <LazyImage src={page.featuredImage} alt='LazyImage' />
</Container> </div>
</Section> </div>
</Page> </div>
) )

2
src/views/Contact.css Normal file
View file

@ -0,0 +1,2 @@
.Contact {
}

View file

@ -1,31 +1,31 @@
import React from 'react' import React from 'react'
import Helmet from 'react-helmet' import Helmet from 'react-helmet'
import Page from '../components/Page'
import PageHeader from '../components/PageHeader' import PageHeader from '../components/PageHeader'
import NetlifyControlledForm from '../components/NetlifyControlledForm' // import NetlifyControlledForm from '../components/NetlifyControlledForm'
import NetlifySimpleForm from '../components/NetlifySimpleForm' import EnquiryFormSimple from '../components/EnquiryFormSimple'
import { Container, Section } from '../components/common' import Content from '../components/Content'
import Marked from 'react-markdown' import './Contact.css'
export default ({ page }) => ( export default ({ page }) => (
<Page> <div className='Contact'>
<PageHeader title={page.title} subtitle='<Contact />' /> <PageHeader title={page.title} subtitle='<Contact />' />
<Section thin> <div className='Section thin'>
<Container> <div className='Container'>
<Marked source={page.content} /> <Content source={page.content} />
<br /> <br />
<h3>{'<NetlifyControlledForm />'}</h3> <h3>{'<NetlifyControlledForm />'}</h3>
<NetlifyControlledForm name={'Controlled Form'} /> {/* <NetlifyControlledForm name={'Controlled Form'} /> */}
<br /> <br />
<h3>{'<NetlifySimpleForm />'}</h3> <h3>{'<NetlifySimpleForm />'}</h3>
<NetlifySimpleForm name='Simple Form' /> <EnquiryFormSimple name='Simple Form' />
<em>Note: these will only work when deployed on Netlify</em> <em>Note: these will only work when deployed on Netlify</em>
<br /> <br />
<em>Also, they are active and I will receive submissions</em> 😉 <em>Also, they are active and I will receive submissions</em> 😉
</Container> </div>
</Section> </div>
<Helmet> <Helmet>
<title>{page.title}</title> <title>{page.title}</title>
</Helmet> </Helmet>
</Page> </div>
) )

2
src/views/Home.css Normal file
View file

@ -0,0 +1,2 @@
.Home {
}

View file

@ -1,23 +1,23 @@
import React from 'react' import React from 'react'
import Helmet from 'react-helmet' import Helmet from 'react-helmet'
import Page from '../components/Page'
import { Container, Section } from '../components/common'
import Content from '../components/Content' import Content from '../components/Content'
import PageHeader from '../components/PageHeader' import PageHeader from '../components/PageHeader'
import './Home.css'
export default ({ page }) => { export default ({ page }) => {
const { title, subtitle } = page const { title, subtitle } = page
return ( return (
<Page> <main className='Home'>
<PageHeader title={title} subtitle={subtitle} /> <PageHeader title={title} subtitle={subtitle} />
<Section thin> <div className='Section thin'>
<Container> <div className='Container'>
<Content source={page.content} /> <Content source={page.content} />
</Container> </div>
</Section> </div>
<Helmet> <Helmet>
<title>{title}</title> <title>{title}</title>
</Helmet> </Helmet>
</Page> </main>
) )
} }

5
src/views/NoMatch.css Normal file
View file

@ -0,0 +1,5 @@
.body--NoMatch .React-Wrap {
display: flex;
flex-direction: column;
justify-content: space-between;
}

View file

@ -1,19 +1,24 @@
import React from 'react' import React from 'react'
import Helmet from 'react-helmet' import Helmet from 'react-helmet'
import Page from '../components/Page'
import PageHeader from '../components/PageHeader'
import { Section, Container } from '../components/common'
export default () => ( import './NoMatch.css'
<Page>
<PageHeader title='404' subtitle='<NoMatch />' /> export default ({ siteUrl }) => (
<Section> <div className='Page'>
<Container taCenter> <section className='Section thick'>
<h3>404 Page Not Found</h3> <div className='Container taCenter'>
</Container> <h1>404 - Page Not Found</h1>
</Section> <p>
We can't find the page you are looking for!<br />Head back to{' '}
<a href={siteUrl}>
{siteUrl.replace(/(^https?:\/\/)/, '').replace(/\/$/, '')}
</a>
</p>
</div>
</section>
<Helmet> <Helmet>
<title>404 Page Not Found</title> <title>404 Page Not Found</title>
<body className='body--NoMatch' />
</Helmet> </Helmet>
</Page> </div>
) )

View file

@ -54,9 +54,9 @@
lodash "^4.2.0" lodash "^4.2.0"
to-fast-properties "^2.0.0" to-fast-properties "^2.0.0"
"@researchgate/react-intersection-observer@^0.5.0": "@researchgate/react-intersection-observer@^0.6.0":
version "0.5.0" version "0.6.0"
resolved "https://registry.yarnpkg.com/@researchgate/react-intersection-observer/-/react-intersection-observer-0.5.0.tgz#c83341fe2d86467816373e9141c37b71b90798d9" resolved "https://registry.yarnpkg.com/@researchgate/react-intersection-observer/-/react-intersection-observer-0.6.0.tgz#505f20b62c8941a035442b61ac448870afb5d8a2"
dependencies: dependencies:
invariant "^2.2.2" invariant "^2.2.2"
prop-types "^15.6.0" prop-types "^15.6.0"
@ -1260,13 +1260,6 @@ buffer@^4.3.0:
ieee754 "^1.1.4" ieee754 "^1.1.4"
isarray "^1.0.0" isarray "^1.0.0"
buffer@^5.0.3:
version "5.0.8"
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.0.8.tgz#84daa52e7cf2fa8ce4195bc5cf0f7809e0930b24"
dependencies:
base64-js "^1.0.2"
ieee754 "^1.1.4"
builtin-modules@^1.0.0, builtin-modules@^1.1.1: builtin-modules@^1.0.0, builtin-modules@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
@ -1776,10 +1769,6 @@ crypto-browserify@^3.11.0:
randombytes "^2.0.0" randombytes "^2.0.0"
randomfill "^1.0.3" randomfill "^1.0.3"
css-color-keywords@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
css-color-names@0.0.4: css-color-names@0.0.4:
version "0.0.4" version "0.0.4"
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
@ -1820,14 +1809,6 @@ css-selector-tokenizer@^0.7.0:
fastparse "^1.1.1" fastparse "^1.1.1"
regexpu-core "^1.0.0" regexpu-core "^1.0.0"
css-to-react-native@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.0.4.tgz#cf4cc407558b3474d4ba8be1a2cd3b6ce713101b"
dependencies:
css-color-keywords "^1.0.0"
fbjs "^0.8.5"
postcss-value-parser "^3.3.0"
css-what@2.1: css-what@2.1:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd"
@ -2844,7 +2825,7 @@ fb-watchman@^2.0.0:
dependencies: dependencies:
bser "^2.0.0" bser "^2.0.0"
fbjs@^0.8.16, fbjs@^0.8.5, fbjs@^0.8.9: fbjs@^0.8.16:
version "0.8.16" version "0.8.16"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
dependencies: dependencies:
@ -3380,10 +3361,6 @@ hoek@4.x.x:
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
hoist-non-react-statics@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb"
hoist-non-react-statics@^2.3.0: hoist-non-react-statics@^2.3.0:
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0"
@ -3733,10 +3710,6 @@ is-fullwidth-code-point@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
is-function@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5"
is-glob@^2.0.0, is-glob@^2.0.1: is-glob@^2.0.0, is-glob@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
@ -3802,12 +3775,6 @@ is-plain-obj@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e"
is-plain-object@^2.0.1:
version "2.0.4"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
dependencies:
isobject "^3.0.1"
is-posix-bracket@^0.1.0: is-posix-bracket@^0.1.0:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
@ -3896,10 +3863,6 @@ isobject@^2.0.0:
dependencies: dependencies:
isarray "1.0.0" isarray "1.0.0"
isobject@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
isomorphic-fetch@^2.1.1: isomorphic-fetch@^2.1.1:
version "2.2.1" version "2.2.1"
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
@ -5238,10 +5201,6 @@ pluralize@^7.0.0:
version "7.0.0" version "7.0.0"
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777"
polished@^1.7.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/polished/-/polished-1.9.0.tgz#76d277d6610dbcb85477dbba1bd1aa8e642741b6"
portfinder@^1.0.9: portfinder@^1.0.9:
version "1.0.13" version "1.0.13"
resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9"
@ -6730,24 +6689,6 @@ style-loader@0.19.0:
loader-utils "^1.0.2" loader-utils "^1.0.2"
schema-utils "^0.3.0" schema-utils "^0.3.0"
styled-components@^2.1.0:
version "2.2.3"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-2.2.3.tgz#154575c269880c840f903f580287dab155cf684c"
dependencies:
buffer "^5.0.3"
css-to-react-native "^2.0.3"
fbjs "^0.8.9"
hoist-non-react-statics "^1.2.0"
is-function "^1.0.1"
is-plain-object "^2.0.1"
prop-types "^15.5.4"
stylis "3.x"
supports-color "^3.2.3"
stylis@3.x:
version "3.4.3"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.4.3.tgz#875bd0db3db37bb6de08f89275fc38ee2e32ee75"
supports-color@^2.0.0: supports-color@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"