feat(page): add favycon home page (#29)
29
components/lazy-image/index.module.scss
Normal file
@ -0,0 +1,29 @@
|
||||
@import '../../styles/variables.scss';
|
||||
|
||||
.root {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: 'object-fit: cover; object-position: center;', sans-serif;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
composes: image;
|
||||
transition: opacity 0.2s linear 0.2s;
|
||||
}
|
||||
|
||||
.normal {
|
||||
composes: image;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
54
components/lazy-image/index.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import React, { useRef, useEffect } from 'react'
|
||||
import { LazyImageFull, ImageState } from 'react-lazy-images'
|
||||
import objectFitImages from 'object-fit-images'
|
||||
|
||||
import styles from './index.module.scss'
|
||||
|
||||
type LazyImageProps = {
|
||||
src: string
|
||||
srcRetina: string
|
||||
srcPlaceholder: string
|
||||
alt: string
|
||||
aspectRatio: string
|
||||
}
|
||||
|
||||
const LazyImage = ({ src, srcRetina, srcPlaceholder, alt, aspectRatio }: LazyImageProps) => {
|
||||
const [widthString, heightString] = aspectRatio.split('/')
|
||||
const width = parseInt(widthString)
|
||||
const height = parseInt(heightString)
|
||||
|
||||
const imgRef = useRef<HTMLImageElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (imgRef.current) {
|
||||
objectFitImages(imgRef.current)
|
||||
}
|
||||
}, [imgRef])
|
||||
|
||||
return (
|
||||
<LazyImageFull src={src} srcSet={`${src} 1x, ${srcRetina} 2x`} alt={alt}>
|
||||
{({ imageProps, imageState, ref }) => {
|
||||
const loaded = imageState === ImageState.LoadSuccess
|
||||
return (
|
||||
<div className={styles.root} ref={ref} style={{ paddingBottom: `${(height / width) * 100 + '%'}` }}>
|
||||
<img
|
||||
className={styles.placeholder}
|
||||
src={srcPlaceholder}
|
||||
alt={imageProps.alt}
|
||||
style={{ opacity: loaded ? 0 : 1 }}
|
||||
/>
|
||||
<img
|
||||
ref={imgRef}
|
||||
className={styles.normal}
|
||||
{...imageProps}
|
||||
alt={imageProps.alt}
|
||||
style={{ opacity: loaded ? 1 : 0 }}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</LazyImageFull>
|
||||
)
|
||||
}
|
||||
|
||||
export { LazyImage }
|
||||
@ -28,6 +28,16 @@
|
||||
.footer {
|
||||
font-size: $font-size-footer;
|
||||
line-height: $line-height-footer;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
|
||||
&:focus,
|
||||
&:active,
|
||||
&:hover {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.superscript {
|
||||
|
||||
@ -43,6 +43,7 @@
|
||||
"classnames": "2.2.6",
|
||||
"multer": "1.4.2",
|
||||
"next": "9.3.4",
|
||||
"object-fit-images": "3.2.4",
|
||||
"preact": "10.4.0",
|
||||
"preact-render-to-string": "5.1.6",
|
||||
"preact-ssr-prepass": "1.0.1",
|
||||
@ -50,6 +51,7 @@
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-dropzone": "10.2.2",
|
||||
"react-lazy-images": "1.1.0",
|
||||
"sharp": "0.25.2",
|
||||
"use-dark-mode": "2.3.1"
|
||||
},
|
||||
@ -71,6 +73,7 @@
|
||||
"@types/classnames": "2.2.10",
|
||||
"@types/multer": "1.4.2",
|
||||
"@types/node": "13.11.1",
|
||||
"@types/object-fit-images": "3.2.0",
|
||||
"@types/react": "16.9.34",
|
||||
"@types/sharp": "0.24.0",
|
||||
"@typescript-eslint/eslint-plugin": "2.27.0",
|
||||
|
||||
@ -1,27 +0,0 @@
|
||||
import React from 'react'
|
||||
import { NextPage } from 'next'
|
||||
import { BaseLayout } from 'components/base-layout'
|
||||
import { Typography } from 'components/typography'
|
||||
import { SEO } from 'components/seo'
|
||||
import { ToolTitle } from 'components/tool-title'
|
||||
|
||||
const Appycon: NextPage = () => (
|
||||
<BaseLayout>
|
||||
<SEO title="Appycon" description="Appycon page description" />
|
||||
<main>
|
||||
<ToolTitle>Appycon</ToolTitle>
|
||||
<Typography variant="largeBody" weight="medium">
|
||||
A small online tool to help you generate your favicon in all the sizes and formats you need. <br />
|
||||
<br />
|
||||
Just drag & drop an image and you will then get a downloadable file alongside some documentation on how to
|
||||
add the favicons.
|
||||
</Typography>
|
||||
<hr />
|
||||
<Typography variant="footer" weight="semiBold" color="gray">
|
||||
Created by <u>4 people</u> on their 2020’s worldwide quarantine. Background image from <u>Unsplash</u>.
|
||||
</Typography>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
)
|
||||
|
||||
export default Appycon
|
||||
@ -1,30 +0,0 @@
|
||||
import React from 'react'
|
||||
import { NextPage } from 'next'
|
||||
import { BaseLayout } from 'components/base-layout'
|
||||
import { Typography } from 'components/typography'
|
||||
import { SEO } from 'components/seo'
|
||||
import { ToolTitle } from 'components/tool-title'
|
||||
|
||||
const Favycon: NextPage = () => (
|
||||
<BaseLayout>
|
||||
<SEO
|
||||
title="Favycon"
|
||||
description="A small online tool to help you generate your favicon in all the sizes and formats you need."
|
||||
/>
|
||||
<main>
|
||||
<ToolTitle>Favycon</ToolTitle>
|
||||
<Typography variant="largeBody" weight="medium">
|
||||
A small online tool to help you generate your favicon in all the sizes and formats you need. <br />
|
||||
<br />
|
||||
Just drag & drop an image and you will then get a downloadable file alongside some documentation on how to
|
||||
add the favicons.
|
||||
</Typography>
|
||||
<hr />
|
||||
<Typography variant="footer" weight="semiBold" color="gray">
|
||||
Created by <u>4 people</u> on their 2020’s worldwide quarantine. Background image from <u>Unsplash</u>.
|
||||
</Typography>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
)
|
||||
|
||||
export default Favycon
|
||||
56
pages/index.module.scss
Normal file
@ -0,0 +1,56 @@
|
||||
@import '../styles/variables.scss';
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
padding: 52px 20px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.info {
|
||||
width: 100%;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.firstParagraph {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.secondParagraph {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.dragAndDrop {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
max-width: 540px;
|
||||
padding: 41px 0 38px;
|
||||
}
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
transform: rotate(2deg);
|
||||
}
|
||||
|
||||
.image {
|
||||
position: absolute;
|
||||
top: 57px;
|
||||
right: 15px;
|
||||
width: 184px;
|
||||
height: 108px;
|
||||
transform: translate(100%, -100%);
|
||||
}
|
||||
@ -1,17 +1,79 @@
|
||||
import React from 'react'
|
||||
import { NextPage } from 'next'
|
||||
import useDarkMode from 'use-dark-mode'
|
||||
import { LazyImage } from 'components/lazy-image'
|
||||
import { BaseLayout } from 'components/base-layout'
|
||||
import { Typography } from 'components/typography'
|
||||
import { SEO } from 'components/seo'
|
||||
import { ToolTitle } from 'components/tool-title'
|
||||
import { DragAndDrop } from 'components/drag-and-drop'
|
||||
|
||||
const Home: NextPage = () => (
|
||||
<BaseLayout>
|
||||
<SEO title="Home" description="Home page description" />
|
||||
<main>
|
||||
<ToolTitle>Favycon</ToolTitle>
|
||||
<ToolTitle>Appycon</ToolTitle>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
)
|
||||
import styles from './index.module.scss'
|
||||
|
||||
const getRandomNumber = (min = 1, max = 3) => Math.floor(Math.random() * max) + min
|
||||
|
||||
const unsplashImagesUrls = [
|
||||
'https://unsplash.com/photos/F573ZRbKOEw',
|
||||
'https://unsplash.com/photos/k0JNJRbrJAs',
|
||||
'https://unsplash.com/photos/f9oQZOk9vnk',
|
||||
]
|
||||
|
||||
const Home: NextPage = () => {
|
||||
const { value: isDark } = useDarkMode(false)
|
||||
const backgroundId = getRandomNumber()
|
||||
const onDrop = (acceptedFiles: File[]) => {
|
||||
console.log('acceptedFiles[0]', acceptedFiles[0])
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseLayout>
|
||||
<SEO
|
||||
title="Favycon"
|
||||
description="A small online tool to help you generate your favicon in all the sizes and formats you need."
|
||||
/>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.info}>
|
||||
<ToolTitle>Favycon</ToolTitle>
|
||||
<Typography variant="largeBody" weight="medium" className={styles.firstParagraph}>
|
||||
A small online tool to help you generate your favicon in all the sizes and formats you need.
|
||||
</Typography>
|
||||
<Typography variant="largeBody" weight="medium" className={styles.secondParagraph}>
|
||||
Just drag & drop an image and you will then get a downloadable file alongside some documentation on how
|
||||
to add the favicons.
|
||||
</Typography>
|
||||
<hr className={styles.lastParagraph} />
|
||||
<Typography variant="footer" weight="semiBold" color="gray">
|
||||
Created by <a href="/#">4 people</a> on their 2020’s worldwide quarantine. Background image from{' '}
|
||||
<a href={unsplashImagesUrls[backgroundId - 1]} target="_blank" rel="noopener noreferrer">
|
||||
Unsplash
|
||||
</a>
|
||||
.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={styles.dragAndDrop}>
|
||||
<div className={styles.background}>
|
||||
<LazyImage
|
||||
src={`/images/unsplash-${backgroundId}@1x.jpg`}
|
||||
srcRetina={`/images/unsplash-${backgroundId}@2x.jpg`}
|
||||
srcPlaceholder={`/images/unsplash-${backgroundId}@placeholder.jpg`}
|
||||
alt="Unsplash"
|
||||
aspectRatio="540/354"
|
||||
/>
|
||||
</div>
|
||||
<DragAndDrop onDrop={onDrop} />
|
||||
<div className={styles.image}>
|
||||
<LazyImage
|
||||
src={`/images/dnd-${isDark ? 'dark' : 'light'}@1x.png`}
|
||||
srcRetina={`/images/dnd-${isDark ? 'dark' : 'light'}@2x.png`}
|
||||
srcPlaceholder={`/images/dnd-${isDark ? 'dark' : 'light'}@1x.png`}
|
||||
alt="Drag and drop here!"
|
||||
aspectRatio="184/108"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</BaseLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
|
||||
BIN
public/images/dnd-dark@1x.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
public/images/dnd-dark@2x.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
public/images/dnd-light@1x.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/images/dnd-light@2x.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
public/images/unsplash-1@1x.jpg
Normal file
|
After Width: | Height: | Size: 263 KiB |
BIN
public/images/unsplash-1@2x.jpg
Normal file
|
After Width: | Height: | Size: 795 KiB |
BIN
public/images/unsplash-1@placeholder.jpg
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
public/images/unsplash-2@1x.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
public/images/unsplash-2@2x.jpg
Normal file
|
After Width: | Height: | Size: 256 KiB |
BIN
public/images/unsplash-2@placeholder.jpg
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
public/images/unsplash-3@1x.jpg
Normal file
|
After Width: | Height: | Size: 264 KiB |
BIN
public/images/unsplash-3@2x.jpg
Normal file
|
After Width: | Height: | Size: 858 KiB |
BIN
public/images/unsplash-3@placeholder.jpg
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
33
yarn.lock
@ -1203,7 +1203,7 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
version "7.9.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06"
|
||||
integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==
|
||||
@ -2444,6 +2444,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/npmlog/-/npmlog-4.1.2.tgz#d070fe6a6b78755d1092a3dc492d34c3d8f871c4"
|
||||
integrity sha512-4QQmOF5KlwfxJ5IGXFIudkeLCdMABz03RcUXu+LCb24zmln8QW6aDjuGl4d4XPVLf2j+FnjelHTP7dvceAFbhA==
|
||||
|
||||
"@types/object-fit-images@3.2.0":
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/object-fit-images/-/object-fit-images-3.2.0.tgz#ffa0b3243ade3b89a2b12a08e92dbbea75b825f4"
|
||||
integrity sha512-ordonpqL1HiYEHDIGxDo/MvyDvGZiNmPpw9bYxys7TXPcCN5WBejOR1Mj/KPCPyjMeaXyLQu7JP5tVi4GBRoWA==
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
@ -10321,6 +10326,11 @@ object-copy@^0.1.0:
|
||||
define-property "^0.2.5"
|
||||
kind-of "^3.0.3"
|
||||
|
||||
object-fit-images@3.2.4:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/object-fit-images/-/object-fit-images-3.2.4.tgz#6c299d38fdf207746e5d2d46c2877f6f25d15b52"
|
||||
integrity sha512-G+7LzpYfTfqUyrZlfrou/PLLLAPNC52FTy5y1CBywX+1/FkxIloOyQXBmZ3Zxa2AWO+lMF0JTuvqbr7G5e5CWg==
|
||||
|
||||
object-inspect@^1.7.0:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
|
||||
@ -11682,6 +11692,14 @@ react-inspector@^4.0.0:
|
||||
is-dom "^1.0.9"
|
||||
prop-types "^15.6.1"
|
||||
|
||||
react-intersection-observer@^6.1.0:
|
||||
version "6.4.2"
|
||||
resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-6.4.2.tgz#a061add707c37ea14e1308c77c2e286719347928"
|
||||
integrity sha512-gL6YrkhniA0tIbyDbUterzBwKh61vHR520rsKULel5T37gG4YP07wnWI3WoqOcKK5bKAu0PZB2FHD7/OjawN+w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.2.0"
|
||||
invariant "^2.2.4"
|
||||
|
||||
react-is@16.8.6:
|
||||
version "16.8.6"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
|
||||
@ -11692,6 +11710,14 @@ react-is@^16.7.0, react-is@^16.8.1:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
||||
react-lazy-images@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/react-lazy-images/-/react-lazy-images-1.1.0.tgz#c865445c96e568a6249db03a2bb2dccd49cb6f50"
|
||||
integrity sha512-h5DHFhkMJyh2qsDl3hXWu6d+On10FsgHtRJ+BH7xjgsFOvsqaii9CEwEESqPJrrAiHo1qrN1LgzrV8X3zctHKA==
|
||||
dependencies:
|
||||
react-intersection-observer "^6.1.0"
|
||||
unionize "^2.1.2"
|
||||
|
||||
react-lifecycles-compat@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
@ -14150,6 +14176,11 @@ union-value@^1.0.0:
|
||||
is-extendable "^0.1.1"
|
||||
set-value "^2.0.1"
|
||||
|
||||
unionize@^2.1.2:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/unionize/-/unionize-2.2.0.tgz#ce88635aa0793f28ab617abeac36172ba0aeb7bc"
|
||||
integrity sha512-lHXiL6LPVuRYBGCLOdUd4GMHoAGqM0HtYHAZcA6pUEiwN1nk+LEYlh8bud7saeL0bkFntJzCPEPVVJeFm3Cqsg==
|
||||
|
||||
uniq@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
|
||||
|
||||