Merge pull request #239 from ruisaraiva19/develop

chore: release
This commit is contained in:
Rui Saraiva 2020-09-11 01:45:30 +01:00 committed by GitHub
commit 2b099d4f49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
109 changed files with 6239 additions and 3245 deletions

View File

@ -51,6 +51,16 @@
"code",
"review"
]
},
{
"login": "miguellteixeira",
"name": "Miguel Teixeira",
"avatar_url": "https://avatars1.githubusercontent.com/u/17954325?v=4",
"profile": "https://github.com/miguellteixeira",
"contributions": [
"code",
"review"
]
}
],
"contributorsPerLine": 7

3
.eslintignore Normal file
View File

@ -0,0 +1,3 @@
node_modules
.next
storybook-static

View File

@ -2,14 +2,14 @@
"parser": "@typescript-eslint/parser",
"extends": [
"react-app",
"prettier",
"plugin:prettier/recommended",
"prettier/react",
"prettier/@typescript-eslint",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking"
],
"plugins": ["@typescript-eslint", "react", "react-hooks", "prettier"],
"parserOptions": {
"project": "./tsconfig.json"
},
@ -17,12 +17,26 @@
"prettier/prettier": "error",
"react/prop-types": "off",
"no-param-reassign": "off",
"react/jsx-filename-extension": [1, { "extensions": [".js", ".ts", "tsx"] }],
"react/jsx-filename-extension": [
1,
{
"extensions": [".js", ".ts", ".tsx"]
}
],
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
"@typescript-eslint/member-delimiter-style": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-ignore": "off"
"@typescript-eslint/ban-ts-ignore": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-extra-semi": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/unbound-method": "off"
},
"overrides": [
{

View File

@ -4,6 +4,7 @@ on: pull_request
jobs:
lint:
name: Lint commit messages
runs-on: ubuntu-latest
steps:
- name: Checkout

View File

@ -4,6 +4,7 @@ on: pull_request
jobs:
code:
name: Lint code
runs-on: ubuntu-latest
steps:
- name: Checkout
@ -11,8 +12,10 @@ jobs:
- name: Use Node.js 12
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: "12"
- name: Install dependencies
run: yarn install --frozen-lockfile &> /dev/null
- name: Lint
run: yarn lint
uses: bahmutov/npm-install@v1
- name: Lint Scripts
run: yarn lint:scripts
- name: Lint CSS
run: yarn lint:styles

View File

@ -7,6 +7,7 @@ on:
jobs:
release:
name: Semantic Release
runs-on: ubuntu-latest
steps:
- name: Checkout
@ -14,9 +15,9 @@ jobs:
- name: Use Node.js 12
uses: actions/setup-node@v1
with:
node-version: 12.x
node-version: "12"
- name: Install dependencies
run: yarn install --frozen-lockfile &> /dev/null
uses: bahmutov/npm-install@v1
- name: Build static assets
run: yarn build
- name: Semantic Release

45
.github/workflows/tests.yml vendored Normal file
View File

@ -0,0 +1,45 @@
name: Tests
on: pull_request
jobs:
wait_deploy:
name: Wait Vercel Deploy
runs-on: ubuntu-latest
outputs:
preview_url: ${{ steps.waitForVercelPreviewDeployment.outputs.url }}
steps:
- name: Wait for Vercel preview deployment to be ready
uses: patrickedqvist/wait-for-vercel-preview@master
id: waitForVercelPreviewDeployment
with:
token: ${{ secrets.GITHUB_TOKEN }}
max_timeout: 120
test_e2e:
name: Run Cypress
needs: wait_deploy
runs-on: ubuntu-16.04
steps:
- uses: actions/checkout@v2
- name: Run Cypress
uses: cypress-io/github-action@v1
with:
config: baseUrl=${{ needs.wait_deploy.outputs.preview_url }}
wait-on: ${{ needs.wait_deploy.outputs.preview_url }}
wait-on-timeout: 120
# after the test run completes
# store videos and any screenshots
# NOTE: screenshots will be generated only if E2E test failed
# thus we store screenshots only on failures
- uses: actions/upload-artifact@v2
if: failure()
with:
name: cypress-screenshots
path: cypress/screenshots
# Test run video was always captured, so this action uses "always()" condition
- uses: actions/upload-artifact@v2
if: always()
with:
name: cypress-videos
path: cypress/videos

178
.gitignore vendored
View File

@ -1,32 +1,162 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# Created by https://www.toptal.com/developers/gitignore/api/now,yarn,node,macos
# Edit at https://www.toptal.com/developers/gitignore?templates=now,yarn,node,macos
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# debug
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
.now
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
### now ###
.now
### yarn ###
# https://yarnpkg.com/advanced/qa#which-files-should-be-gitignored
# .yarn/unplugged and .yarn/build-state.yml should likely always be ignored since
# they typically hold machine-specific build artifacts. Ignoring them might however
# prevent Zero-Installs from working (to prevent this, set enableScripts to false).
.yarn/unplugged
.yarn/build-state.yml
# .yarn/cache and .pnp.* may be safely ignored, but you'll need to run yarn install
# to regenerate them between each branch switch.
# Uncomment the following lines if you're not using Zero-Installs:
# .yarn/cache
# .pnp.*
# End of https://www.toptal.com/developers/gitignore/api/now,yarn,node,macos
# Storybook
storybook-static

View File

@ -1,7 +1,7 @@
{
"hooks": {
"pre-commit": "lint-staged",
"pre-push": "npm run lint",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
"hooks": {
"pre-commit": "lint-staged",
"pre-push": "npm run lint",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
12.18.3

View File

@ -2,11 +2,8 @@ const path = require('path')
// Export a function. Accept the base config as the only param.
module.exports = {
webpackFinal: async (baseConfig) => {
const nextConfig = require('../next.config.js')
// Make whatever fine-grained changes you need
baseConfig.module.rules.push({
webpackFinal: async (config) => {
config.module.rules.push({
test: /\.scss$/,
use: [
'style-loader',
@ -23,6 +20,6 @@ module.exports = {
})
// merge whatever from nextConfig into the webpack config storybook will use
return { ...baseConfig }
return config
},
}

View File

@ -5,8 +5,14 @@
"stylelint-config-css-modules",
"stylelint-config-prettier"
],
"plugins": ["stylelint-prettier"],
"plugins": ["stylelint-scss", "stylelint-prettier"],
"rules": {
"prettier/prettier": true
"prettier/prettier": true,
"at-rule-no-unknown": [
true,
{
"ignoreAtRules": ["mixin", "include"]
}
]
}
}

10
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,10 @@
{
"recommendations": [
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
"syler.sass-indented",
"stylelint.vscode-stylelint",
"knisterpeter.vscode-commitizen"
],
"unwantedRecommendations": ["esbenp.prettier-vscode"]
}

View File

@ -7,7 +7,7 @@
A small online tool to help you generate your favicon in all the sizes and formats you need.
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-4-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
![Release](https://github.com/ruisaraiva19/favycon/workflows/Release/badge.svg)
![Lint](https://github.com/ruisaraiva19/favycon/workflows/Lint/badge.svg)
@ -110,14 +110,15 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://ruisaraiva.com"><img src="https://avatars2.githubusercontent.com/u/7356098?v=4" width="100px;" alt=""/><br /><sub><b>Rui Saraiva</b></sub></a><br /><a href="#infra-ruisaraiva19" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/ruisaraiva19/favycon/commits?author=ruisaraiva19" title="Code">💻</a> <a href="https://github.com/ruisaraiva19/favycon/commits?author=ruisaraiva19" title="Documentation">📖</a> <a href="#question-ruisaraiva19" title="Answering Questions">💬</a> <a href="#maintenance-ruisaraiva19" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://github.com/eduardoPinto12"><img src="https://avatars1.githubusercontent.com/u/45365656?v=4" width="100px;" alt=""/><br /><sub><b>Eduardo Pinto</b></sub></a><br /><a href="https://github.com/ruisaraiva19/favycon/commits?author=eduardoPinto12" title="Code">💻</a> <a href="https://github.com/ruisaraiva19/favycon/pulls?q=is%3Apr+reviewed-by%3AeduardoPinto12" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/otsugua"><img src="https://avatars2.githubusercontent.com/u/9093629?v=4" width="100px;" alt=""/><br /><sub><b>Augusto Lopes</b></sub></a><br /><a href="#design-otsugua" title="Design">🎨</a></td>
<td align="center"><a href="https://rgllm.com"><img src="https://avatars3.githubusercontent.com/u/9056941?v=4" width="100px;" alt=""/><br /><sub><b>Rogério Moreira</b></sub></a><br /><a href="https://github.com/ruisaraiva19/favycon/commits?author=rgllm" title="Code">💻</a> <a href="https://github.com/ruisaraiva19/favycon/pulls?q=is%3Apr+reviewed-by%3Argllm" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://ruisaraiva.com"><img src="https://avatars2.githubusercontent.com/u/7356098?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rui Saraiva</b></sub></a><br /><a href="#infra-ruisaraiva19" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/toolslab/favycon/commits?author=ruisaraiva19" title="Code">💻</a> <a href="https://github.com/toolslab/favycon/commits?author=ruisaraiva19" title="Documentation">📖</a> <a href="#question-ruisaraiva19" title="Answering Questions">💬</a> <a href="#maintenance-ruisaraiva19" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://github.com/eduardoPinto12"><img src="https://avatars1.githubusercontent.com/u/45365656?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eduardo Pinto</b></sub></a><br /><a href="https://github.com/toolslab/favycon/commits?author=eduardoPinto12" title="Code">💻</a> <a href="https://github.com/toolslab/favycon/pulls?q=is%3Apr+reviewed-by%3AeduardoPinto12" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/otsugua"><img src="https://avatars2.githubusercontent.com/u/9093629?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Augusto Lopes</b></sub></a><br /><a href="#design-otsugua" title="Design">🎨</a></td>
<td align="center"><a href="https://rgllm.com"><img src="https://avatars3.githubusercontent.com/u/9056941?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Rogério Moreira</b></sub></a><br /><a href="https://github.com/toolslab/favycon/commits?author=rgllm" title="Code">💻</a> <a href="https://github.com/toolslab/favycon/pulls?q=is%3Apr+reviewed-by%3Argllm" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/miguellteixeira"><img src="https://avatars1.githubusercontent.com/u/17954325?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Miguel Teixeira</b></sub></a><br /><a href="https://github.com/toolslab/favycon/commits?author=miguellteixeira" title="Code">💻</a> <a href="https://github.com/toolslab/favycon/pulls?q=is%3Apr+reviewed-by%3Amiguellteixeira" title="Reviewed Pull Requests">👀</a></td>
</tr>
</table>
<!-- markdownlint-enable -->
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->

View File

@ -2,4 +2,5 @@
.root {
padding: 0;
overflow: hidden;
}

View File

@ -10,18 +10,24 @@
border-radius: 4px;
transition: all 0.2s ease;
@include breakpoint-md {
padding: 9px 12px;
}
&:hover,
&:focus,
&:active {
outline: none;
}
&:hover {
transform: translate3d(0, -2px, 0);
}
@media (pointer: fine) {
&:hover {
transform: translate3d(0, -2px, 0);
}
&:active {
transform: translate3d(0, 0, 0);
&:active {
transform: translate3d(0, 0, 0);
}
}
}
@ -52,14 +58,34 @@
padding: 5px 0;
background: transparent;
border: 1px solid transparent;
@include breakpoint-md {
padding: 9px 0;
}
}
:global(.dark-mode) .transparent {
@include breakpoint-md {
background: $dark-grey;
}
}
.modalClose {
composes: base;
padding: 5px 16px;
background-color: $off-white;
border: 1px solid $light-grey;
border-radius: 18px;
}
.regularTransparent {
composes: transparent;
composes: base;
padding: 0;
font-size: $font-size-regular-body;
line-height: $line-height-regular-body;
text-decoration: none;
background: transparent;
border: 1px solid transparent;
border: 0;
}
@ -89,6 +115,10 @@
:global(.dark-mode) .gray {
color: $grey-dark;
@include breakpoint-md {
color: $off-white-dark;
}
}
.white {
@ -150,3 +180,13 @@
border-color: $green-hover;
}
}
.bgDarkGray {
background: $dark-grey;
border-color: $dark-grey;
&:hover {
background: $darker-grey;
border-color: $darker-grey;
}
}

View File

@ -6,10 +6,10 @@ import styles from './index.module.scss'
type ButtonProps = {
children: PropTypes.ReactNodeLike
variant: 'primary' | 'transparent' | 'regularTransparent'
variant: 'primary' | 'transparent' | 'regularTransparent' | 'modalClose'
weight: 'regular' | 'medium' | 'semiBold' | 'bold'
color: 'black' | 'gray' | 'white' | 'link'
background: 'bgLink' | 'bgGreen'
background: 'bgLink' | 'bgGreen' | 'bgDarkGray'
}
const Button = ({

View File

@ -7,13 +7,15 @@ type CodeHighlightProps = {
template: string
}
const CodeHighlight = ({ template }: CodeHighlightProps) => {
const CodeHighlight = React.forwardRef<HTMLPreElement, CodeHighlightProps>(({ template }, ref) => {
const htmlCode = Prism.highlight(template, Prism.languages.markup, 'markup')
return (
<pre className={styles.root}>
<pre className={styles.root} ref={ref}>
<code className="language-markup" dangerouslySetInnerHTML={{ __html: htmlCode }}></code>
</pre>
)
}
})
CodeHighlight.displayName = 'CodeHighlight'
export { CodeHighlight }

View File

@ -72,19 +72,23 @@
.sun,
.moon {
path {
fill: #657584;
fill: $grey-dark;
}
}
}
:global(.light-mode) .label {
&:hover path {
fill: $link;
@media (pointer: fine) {
&:hover path {
fill: $link;
}
}
}
:global(.dark-mode) .label {
&:hover path {
fill: $link-dark;
@media (pointer: fine) {
&:hover path {
fill: $link-dark;
}
}
}

View File

@ -9,10 +9,28 @@
border-radius: 8px;
box-shadow: 0 2px 4px rgba($black, 0.12);
transition: background 0.2s ease;
@include breakpoint-md {
max-width: 100%;
height: 100%;
border-radius: 0;
}
&.transparent {
@include breakpoint-md {
background: transparent;
}
}
}
:global(.dark-mode) .root {
background: $dark-black;
&.transparent {
@include breakpoint-md {
background: transparent;
}
}
}
.container {
@ -25,6 +43,21 @@
pointer-events: none;
opacity: 0.32;
}
@include breakpoint-md {
display: flex;
flex-direction: column;
width: 100%;
height: calc(100% - 68px);
margin-top: 68px;
border-radius: 0;
}
&.transparent {
@include breakpoint-md {
padding: 0;
}
}
}
.dashed {
@ -67,6 +100,13 @@
border-radius: 6px;
transition: background 0.2s ease;
@include breakpoint-md {
flex-grow: 1;
margin-bottom: 0;
background: transparent;
border-radius: 0;
}
&:hover {
cursor: pointer;
@ -79,6 +119,10 @@
:global(.dark-mode) .dropZoneWrapper {
background: $dark-grey;
@include breakpoint-md {
background: transparent;
}
&:hover {
.dashed rect {
stroke: $off-white-dark;
@ -95,6 +139,32 @@
}
}
.dropZoneFooter {
transition: background 0.2s ease;
@include breakpoint-md {
padding: 16px 24px 24px;
padding-bottom: env(safe-area-inset-bottom);
background: rgba($white, 0.88);
border-radius: 16px 16px 0 0;
box-shadow: 0 -2px 4px rgba($black, 0.12);
@supports (padding: max(1px)) {
padding-bottom: max(24px, env(safe-area-inset-bottom));
}
p {
font-size: 15px;
}
}
}
:global(.dark-mode) .dropZoneFooter {
@include breakpoint-md {
background: rgba($dark-black, 0.88);
}
}
.dropZone {
position: relative;
z-index: 2;
@ -126,25 +196,55 @@
&.dragActive {
height: 48px;
}
@include breakpoint-md {
font-size: 16px;
font-weight: 600;
line-height: 28px;
color: $off-white-dark;
}
&.mobileUploadText {
@include breakpoint-md-up {
display: none;
}
}
&.desktopUploadText {
/* smartphones, touchscreens */
@media (hover: none) and (pointer: coarse) {
display: none;
}
@include breakpoint-md {
display: none;
}
}
}
:global(.dark-mode) .imageUploadText {
color: $white-dark;
@include breakpoint-md {
color: $off-white-dark;
}
}
.info {
display: flex;
align-items: center;
align-items: flex-start;
margin-top: 8px;
letter-spacing: 0.2px;
svg {
flex: 0 0 20px;
margin-right: 8px;
@include breakpoint-md {
height: 24px;
}
}
strong {
padding: 0 4px;
font-weight: 700;
}
}
@ -209,6 +309,7 @@
.imageInfo {
display: flex;
flex-wrap: wrap;
padding: 16px 0;
border-top: 1px solid $light-grey;
transition: all 0.2s ease;
@ -222,17 +323,16 @@
padding: 8px 0 16px;
border-top: 1px solid $light-grey;
transition: all 0.2s ease;
@include breakpoint-md {
padding: 24px 0;
}
}
:global(.dark-mode) .imageOptionsWrapper {
border-top: 1px solid $dark-grey;
}
.imageOptions {
display: flex;
padding: 8px 0 16px;
}
.imageInfoItem {
display: flex;
align-items: center;
@ -241,6 +341,24 @@
span {
margin-right: 8px;
}
@include breakpoint-md {
margin-top: 8px;
margin-bottom: 8px;
}
}
.imageOptions {
display: flex;
flex-wrap: wrap;
padding: 8px 0 16px;
.imageInfoItem {
@include breakpoint-md {
margin-top: 0;
margin-bottom: 24px;
}
}
}
.pwaCheck {
@ -259,6 +377,10 @@
align-items: center;
justify-content: center;
margin: 16px 0 12px;
@include breakpoint-md {
align-items: flex-end;
}
}
.preview {
@ -269,15 +391,39 @@
background: $white;
box-shadow: 0 2px 4px rgba($black, 0.12);
@include breakpoint-md {
width: 96px;
height: 96px;
margin-right: 0;
}
img {
width: 40px;
height: 40px;
object-fit: contain;
@include breakpoint-md {
width: 96px;
height: 96px;
}
}
&.circle,
&.circle img {
border-radius: 24px;
@include breakpoint-md {
width: 52px;
height: 52px;
border-radius: 26px;
}
}
&.circle {
@include breakpoint-md {
z-index: 1;
transform: translate3d(21px, 3px, 0);
}
}
&.square,
@ -285,12 +431,31 @@
width: 48px;
height: 48px;
border-radius: 4px;
@include breakpoint-md {
width: 96px;
height: 96px;
border-radius: 8px;
}
}
&.rounded,
&.rounded img {
margin-right: 0;
border-radius: 14px;
@include breakpoint-md {
width: 40px;
height: 40px;
border-radius: 13px;
}
}
&.rounded {
@include breakpoint-md {
z-index: 1;
transform: translate3d(-12px, 3px, 0);
}
}
}
@ -339,14 +504,32 @@
padding: 23px 24px 24px 24px;
border-top: 1px solid $light-grey;
transition: all 0.2s ease;
&.spaceBetween {
justify-content: space-between;
}
@include breakpoint-md {
flex-direction: column-reverse;
padding: 11px 24px 12px 24px;
border-top: 1px solid transparent;
> * {
margin-bottom: 12px;
}
}
@include breakpoint-xs {
align-items: stretch;
}
}
:global(.dark-mode) .imageFooter {
border-top: 1px solid $dark-grey;
}
.spaceBetween {
justify-content: space-between;
@include breakpoint-md {
border-top: 1px solid transparent;
}
}
.makeNewOne {
@ -377,12 +560,20 @@
.imageGenerate {
margin-left: 24px;
@include breakpoint-md {
margin-left: 0;
}
}
.modalContainer {
max-width: 730px;
height: 100%;
margin: 0 auto;
@include breakpoint-md {
padding-top: 20px;
}
}
.modalHeader {
@ -392,13 +583,23 @@
margin-bottom: 10px;
}
.modalCode {
height: 100%;
max-height: 488px;
padding: 28px;
color: $white;
background: $darker-grey;
border-radius: 8px;
.modalHeaderMobile {
display: none;
padding: 36px 32px 8px;
margin-bottom: 0;
@include breakpoint-md {
display: flex;
background: $dark-black;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
}
}
.modalHeaderDesktop {
@include breakpoint-md {
display: none;
}
}
.overlay {
@ -422,8 +623,8 @@
width: 100%;
max-width: 730px;
height: 100%;
max-height: 536px;
padding: 0;
max-height: 538px;
padding: 2px 0 0;
overflow: auto;
background: transparent;
border: none;
@ -431,6 +632,11 @@
outline: none;
transform: translate(-50%, -50%);
-webkit-overflow-scrolling: touch;
@include breakpoint-md {
max-height: 100vh;
padding: 0;
}
}
.copyWrapper {
@ -460,3 +666,73 @@
:global(.dark-mode) .copyWrapper::before {
color: $off-white-dark;
}
.modalCode {
position: relative;
height: 100%;
max-height: 488px;
padding: 28px;
color: $white;
background: $dark-black;
border-radius: 8px;
@include breakpoint-md {
max-height: calc(100% - 84px);
border-radius: 0;
}
&::before {
z-index: 2;
@include breakpoint-md {
position: absolute;
right: -1px;
bottom: 28px;
left: 0;
height: 136px;
content: '';
background: linear-gradient(to bottom, rgba($dark-black, 0), rgba($dark-black, 1));
}
}
.copyWrapper {
z-index: 3;
display: none;
@include breakpoint-md {
position: absolute;
right: 24px;
bottom: 80px;
left: 24px;
display: block;
button {
width: 100%;
}
}
}
pre {
position: relative;
z-index: 1;
@include breakpoint-md {
max-height: calc(100% - 68px);
padding-bottom: 104px;
}
}
pre code {
@include breakpoint-md {
white-space: pre-wrap;
}
}
}
.modalCodeTitle {
display: none;
@include breakpoint-md {
display: block;
margin-bottom: 20px;
}
}

View File

@ -1,9 +1,11 @@
import dynamic from 'next/dynamic'
import React, { useState, useEffect, useCallback } from 'react'
import React, { useState, useEffect, useCallback, useRef } from 'react'
import classnames from 'classnames'
import { useDropzone, FileRejection } from 'react-dropzone'
import { CSSTransition } from 'react-transition-group'
import { useDrag } from 'react-use-gesture'
import { headTemplate } from 'utils/favicon'
import { isTouchCapable } from 'utils/device'
import { Typography } from 'components/typography'
import { CodeHighlight } from 'components/code-highlight'
import { SvgDropZone } from 'components/svgs/svg-drop-zone'
@ -23,6 +25,8 @@ import { Button } from 'components/button'
import { SvgCheck } from 'components/svgs/svg-check'
import { SvgError } from 'components/svgs/svg-error'
import { Checkbox } from 'components/checkbox'
import { useToggle } from 'hooks/use-toggle'
import { useMediaQueryContext } from 'components/media-query-provider'
import styles from './index.module.scss'
@ -31,11 +35,12 @@ const Clipboard = dynamic(() => import('react-clipboard.js'))
type DragAndDropProps = {
onFile: (hasFile: boolean) => void
onGenerate: (image: File, pwa: boolean) => Promise<ArrayBuffer>
onGenerate: (image: File, pwa: boolean, darkMode: boolean) => Promise<ArrayBuffer>
onError: (message: string) => void
}
const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
const { isMobile } = useMediaQueryContext()
const [imageSizes, setImageSizes] = useState({ width: 0, height: 0 })
const [image, setImage] = useState<File>()
const [imageBase64, setImageBase64] = useState('')
@ -48,10 +53,11 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
const [is310px, setIs310px] = useState(false)
const [is512px, setIs512px] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [pwa, setPwa] = useState(false)
const [darkMode, setDarkMode] = useState(false)
const [pwa, togglePwa] = useToggle(false)
const [darkMode, toggleDarkMode] = useToggle(false)
const [zipData, setZipData] = useState<ArrayBuffer>()
const [isModalOpen, setIsModalOpen] = useState(false)
const modalBodyRef = useRef<HTMLPreElement>(null)
const [copied, setCopied] = useState(false)
const PWADisabled = !isSvg && !is512px
let sizesCount = 16
@ -62,6 +68,22 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
sizesCount += 3
}
const bind = useDrag(
(state) => {
const {
swipe, // [swipeX, swipeY] 0 if no swipe detected, -1 or 1 otherwise
} = state
const isSwipeDown = swipe[1] === 1
if (isSwipeDown && modalBodyRef.current?.scrollTop === 0) {
setIsModalOpen(false)
}
},
{
enabled: isTouchCapable(),
filterTaps: true,
}
)
const generateImgFromCanvas = useCallback(() => {
const canvas = document.createElement('canvas')
canvas.width = imageSizes.width
@ -84,32 +106,37 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
if (image) {
reader.readAsDataURL(image)
const matches = /(.+)(\..+)$/.exec(image.name) as RegExpExecArray
const name = matches[1].length > 30 ? `${matches[1].slice(0, 30)}..` : matches[1].slice(0, 30)
const length = isMobile ? 15 : 30
const name = matches[1].length > length ? `${matches[1].slice(0, length)}..` : matches[1].slice(0, length)
setImageData({ name, extension: matches[2] })
}
}, [image, zipData, generateImgFromCanvas])
}, [image, zipData, generateImgFromCanvas, isMobile])
useEffect(() => {
if (!image) {
return
} else if (isPngOrSvg && isSquare) {
} else if ((isPngOrSvg && isSquare && is310px) || isSvg) {
setTitle('Good to go!')
} else if (isSquare && isPngOrSvg && !is310px) {
setTitle('Your image is lower than 310px')
} else if ((!isSquare && !isPngOrSvg) || (!isSquare && !is310px) || (!isPngOrSvg && !is310px)) {
setTitle('Your image has a few issues')
} else if (isSquare && isPngOrSvg && !is310px && !isSvg) {
setTitle('Your image should be higher than 310px')
} else if ((isSquare && !isPngOrSvg && !is310px) || (!isSquare && !isPngOrSvg && is310px)) {
setTitle('Your image has a few problems')
} else if (isSquare && !isPngOrSvg && is310px) {
setTitle('Your image should be in PNG or SVG')
} else if (!isSquare && isPngOrSvg && is310px) {
setTitle('Your image should be squared')
} else {
setTitle('Your image is not in a good place')
}
}, [image, isSquare, is310px, isPngOrSvg])
}, [image, isSquare, is310px, isPngOrSvg, isSvg])
const generateFavicon = async (file: File) => {
try {
setIsLoading(true)
const zip = await onGenerate(file, pwa)
const zip = await onGenerate(file, pwa, darkMode)
setZipData(zip)
} catch (error) {
console.log(error)
onError(error.message || 'Something went wrong. Please try again.')
} finally {
setIsLoading(false)
}
@ -167,8 +194,12 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
const [isHover, setIsHover] = useState(false)
return (
<div className={styles.root}>
<div className={classnames(styles.container, { [styles.loading]: isLoading })}>
<div className={classnames(styles.root, { [styles.transparent]: !image })}>
<div
className={classnames(styles.container, {
[styles.loading]: isLoading,
[styles.transparent]: !image,
})}>
{!image && !zipData && (
<>
<div
@ -176,7 +207,7 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}>
<SvgDropZone className={classnames(styles.dashed, { [styles.dragActive]: isDragActive })} />
<div {...getRootProps()} className={styles.dropZone}>
<div {...getRootProps()} className={styles.dropZone} data-cy="drag-and-drop">
<input {...getInputProps()} />
<div className={styles.imageUpload}>
<SvgImageUpload active={isDragActive || isHover} />
@ -184,7 +215,23 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
<Typography
variant="regularBody"
weight="medium"
className={classnames(styles.imageUploadText, { [styles.dragActive]: isDragActive })}>
className={classnames(styles.imageUploadText, styles.mobileUploadText, {
[styles.dragActive]: isDragActive,
})}
data-cy="drag-and-drop-text">
<span>
Generate all the sizes for your
<br />
favicon. Tap to upload a file.
</span>
</Typography>
<Typography
variant="regularBody"
weight="medium"
className={classnames(styles.imageUploadText, styles.desktopUploadText, {
[styles.dragActive]: isDragActive,
})}
data-cy="drag-and-drop-text">
<CSSTransition in={!isDragActive} timeout={200} classNames="collapse" unmountOnExit>
<span>Drag &amp;&nbsp;</span>
</CSSTransition>
@ -206,13 +253,17 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
</Typography>
</div>
</div>
{/* <button onClick={() => setInProp(true)}>toggle</button> */}
<Typography variant="regularBody" weight="medium" className={styles.info}>
<SvgInfo /> We recommend a square <strong>PNG</strong> or <strong>SVG</strong> with at least 310px
</Typography>
<Typography variant="regularBody" weight="medium" className={styles.info}>
<SvgInfo /> No data or images are stored
</Typography>
<div className={styles.dropZoneFooter}>
<Typography variant="regularBody" weight="medium" className={styles.info}>
<SvgInfo />{' '}
<span>
We recommend a square <strong>PNG</strong> or <strong>SVG</strong> with at least 310px
</span>
</Typography>
<Typography variant="regularBody" weight="medium" className={styles.info}>
<SvgInfo /> No data or images are stored
</Typography>
</div>
</>
)}
{image && !zipData && (
@ -223,7 +274,7 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
<img src={imageBase64} alt="Favicon preview" />
</div>
<div className={styles.imagePreviewInfo}>
<Typography variant="title" weight="bold" className={styles.filename}>
<Typography variant="title" weight="bold" className={styles.filename} data-cy="preview-title">
{title}
</Typography>
<Typography
@ -232,11 +283,11 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
color="gray"
tag="div"
className={styles.filenameWrapper}>
<span className={styles.filename}>
<span className={styles.filename} data-cy="preview-filename">
{imageData.name}
{imageData.extension}
</span>
<span className={styles.fileSize}>
<span className={styles.fileSize} data-cy="preview-file-size">
({imageSizes.width}x{imageSizes.height})
</span>
</Typography>
@ -244,19 +295,21 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
</div>
<div className={styles.imageInfo}>
<div className={styles.imageInfoItem}>
<span>{isSquare ? <SvgCheck /> : <SvgError />}</span>
<span data-cy="preview-square-image">{isSquare ? <SvgCheck /> : <SvgError />}</span>
<Typography variant="regularBody" weight="semiBold">
Square image
</Typography>
</div>
<div className={styles.imageInfoItem}>
<span>{isPngOrSvg ? <SvgCheck /> : <SvgError />}</span>
<span data-cy="preview-png-svg-image">{isPngOrSvg ? <SvgCheck /> : <SvgError />}</span>
<Typography variant="regularBody" weight="semiBold">
PNG or SVG
</Typography>
</div>
<div className={styles.imageInfoItem}>
<span>{isSvg || is310px ? <SvgCheck disabled={isSvg} /> : <SvgError />}</span>
<span data-cy="preview-310px-image">
{isSvg || is310px ? <SvgCheck disabled={isSvg} /> : <SvgError />}
</span>
<Typography variant="regularBody" weight="semiBold" muted={isSvg}>
310px or higher
</Typography>
@ -268,7 +321,12 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
</Typography>
<div className={styles.imageOptions}>
<div className={styles.imageInfoItem}>
<Checkbox name="pwa" id="pwa" disabled={PWADisabled} onChange={() => setPwa(!pwa)}>
<Checkbox
name="pwa"
id="pwa"
disabled={PWADisabled}
onChange={togglePwa}
data-cy="preview-pwa-compatible">
<Typography variant="regularBody" weight="semiBold" muted={PWADisabled}>
PWA compatible
</Typography>
@ -279,12 +337,17 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
</Checkbox>
</div>
<div className={styles.imageInfoItem}>
<Checkbox name="dark" id="dark" disabled onChange={() => setDarkMode(!darkMode)}>
<Checkbox
name="dark"
id="dark"
disabled
onChange={toggleDarkMode}
data-cy="preview-dark-mode-version">
<Typography variant="regularBody" weight="semiBold" muted>
Dark Mode version
</Typography>
<Typography variant="footer" weight="semiBold" color="green" muted>
Available soon
Coming soon
</Typography>
</Checkbox>
</div>
@ -358,37 +421,52 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
Download Favicon
</Button>
</div>
<Modal
ariaHideApp={false}
isOpen={isModalOpen}
onRequestClose={() => setIsModalOpen(false)}
className={styles.content}
overlayClassName={styles.overlay}
closeTimeoutMS={200}
contentLabel="Code generated">
<div className={styles.modalContainer}>
<div className={styles.modalHeader}>
<Typography variant="title" weight="bold">
Insert the following code in the &lt;head&gt; section of your pages:
</Typography>
<Clipboard
component="div"
data-clipboard-text={headTemplate(undefined, isSvg, pwa)}
onSuccess={onCopy}>
<div className={classnames(styles.copyWrapper, { [styles.copied]: copied })}>
<Button color="white" background="bgLink">
Copy code
</Button>
</div>
</Clipboard>
</div>
<div className={styles.modalCode}>
<CodeHighlight template={headTemplate(undefined, isSvg, pwa)} />
</div>
</div>
</Modal>
</>
)}
<Modal
ariaHideApp={false}
isOpen={isModalOpen}
onRequestClose={() => setIsModalOpen(false)}
className={styles.content}
overlayClassName={styles.overlay}
closeTimeoutMS={200}
contentLabel="Code generated">
<div className={styles.modalContainer} {...bind()}>
<div className={classnames(styles.modalHeader, styles.modalHeaderMobile)}>
<Typography variant="extraLargeTitle" weight="extraBold" color="white" colorImmutable>
Code
</Typography>
<Button variant="modalClose" color="white" background="bgDarkGray" onClick={() => setIsModalOpen(false)}>
Close
</Button>
</div>
<div className={classnames(styles.modalHeader, styles.modalHeaderDesktop)}>
<Typography variant="title" weight="bold">
Insert the following code in the &lt;head&gt; section of your pages:
</Typography>
<Clipboard component="div" data-clipboard-text={headTemplate(undefined, isSvg, pwa)} onSuccess={onCopy}>
<div className={classnames(styles.copyWrapper, { [styles.copied]: copied })}>
<Button color="white" background="bgLink">
Copy code
</Button>
</div>
</Clipboard>
</div>
<div className={styles.modalCode}>
<Typography variant="title" weight="bold" color="white" colorImmutable className={styles.modalCodeTitle}>
Insert the following code in the &lt;head&gt; section of your pages:
</Typography>
<CodeHighlight ref={modalBodyRef} template={headTemplate(undefined, isSvg, pwa)} />
<Clipboard component="div" data-clipboard-text={headTemplate(undefined, isSvg, pwa)} onSuccess={onCopy}>
<div className={classnames(styles.copyWrapper)}>
<Button color="white" background="bgLink">
{copied ? 'Copied!' : 'Copy code'}
</Button>
</div>
</Clipboard>
</div>
</div>
</Modal>
</div>
</div>
)

View File

@ -4,6 +4,7 @@
position: absolute;
top: 26px;
right: 72px;
z-index: 10;
display: flex;
align-items: center;
padding: 6px 13px 6px 16px;
@ -11,6 +12,21 @@
border-radius: 8px;
box-shadow: 0 2px 4px rgba($black, 0.12);
@include breakpoint-md {
top: 13px;
right: 12px;
left: 12px;
justify-content: center;
max-width: 400px;
margin: auto;
}
.content {
@include breakpoint-md {
font-size: 14px;
}
}
svg {
margin-left: 13px;
}

View File

@ -17,12 +17,12 @@ const FavyconError = ({ error, clearError }: FavyconErrorProps) => {
if (error) {
let endTimeout: number
setShowError(true)
const errorTimeout = setTimeout(() => {
const errorTimeout = window.setTimeout(() => {
setShowError(false)
endTimeout = setTimeout(() => {
endTimeout = window.setTimeout(() => {
clearError()
}, 300) as any
}, 5300) as any
}, 300)
}, 5300)
return () => {
clearTimeout(errorTimeout)
clearTimeout(endTimeout)
@ -33,7 +33,12 @@ const FavyconError = ({ error, clearError }: FavyconErrorProps) => {
return (
<CSSTransition in={showError} timeout={300} classNames="error" unmountOnExit>
<div className={classnames(styles.error)}>
<Typography variant="regularBody" color="white" weight="semiBold">
<Typography
className={classnames(styles.content)}
variant="regularBody"
color="white"
colorImmutable={true}
weight="semiBold">
{error}
</Typography>
<svg height={20} width={20}>

View File

@ -1,8 +1,50 @@
@import '../../styles/variables.scss';
.root {
position: relative;
width: 100%;
max-width: 350px;
@include breakpoint-md {
position: absolute;
top: 32px;
right: 72px;
z-index: 3;
display: block;
width: 80px;
max-width: 100%;
}
}
.bodyMobile {
display: none;
@include breakpoint-md {
display: block;
}
}
.bodyDesktop {
@include breakpoint-md {
display: none;
}
}
.modalBodyScroll {
height: calc(100% - 96px);
overflow: auto;
}
.button {
color: $black !important;
background: $light-grey !important;
border: 1px solid $light-grey !important;
}
:global(.dark-mode) .button {
color: $off-white-dark !important;
background: $dark-grey !important;
border: 1px solid $dark-grey !important;
}
.firstParagraph {
@ -14,11 +56,15 @@
margin-top: 16px;
margin-bottom: 60px;
letter-spacing: 0.2px;
@include breakpoint-md {
margin-bottom: 24px;
}
}
:global(.dark-mode) .firstParagraph,
:global(.dark-mode) .secondParagraph {
color: $white-dark;
color: $white-dark !important;
}
.peopleButton {
@ -47,6 +93,10 @@
color: $link;
}
}
@include breakpoint-md {
margin-top: 12px;
}
}
:global(.dark-mode) .footer {
@ -91,27 +141,83 @@
box-shadow: 0 2px 4px rgba($black, 0.12);
transform: translate(-50%, -50%);
-webkit-overflow-scrolling: touch;
@include breakpoint-md {
height: 100%;
max-height: 100vh;
padding-top: 20px;
background: transparent;
border-radius: 0;
}
}
:global(.dark-mode) .content {
background: $dark-black;
@include breakpoint-md {
background: transparent;
}
}
.modalContainer {
max-width: 572px;
padding: 0 16px;
margin: 0 auto;
background: $white;
@include breakpoint-md {
height: 100%;
padding: 0;
border-top-left-radius: 20px;
border-top-right-radius: 20px;
> * {
padding: 0 32px;
}
}
}
:global(.dark-mode) .modalContainer {
background: $dark-black;
}
.modalHeaderTop {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28px 0 16px;
}
.modalHeaderMobile {
display: none;
@include breakpoint-md {
display: block;
}
}
.modalHeader {
padding-top: 48px;
text-align: center;
@include breakpoint-md {
text-align: left;
}
}
.logoMobile {
width: 52px;
height: 52px;
}
.logo {
width: 96px;
height: 96px;
margin-bottom: 16px;
@include breakpoint-md {
display: none;
}
}
.title {
@ -122,6 +228,12 @@
margin-bottom: 24px;
}
:global(.dark-mode) .subTitle {
@include breakpoint-md {
color: $white-dark !important;
}
}
.hr {
padding: 0;
margin: 0;
@ -138,6 +250,10 @@
display: flex;
width: calc(50% - 15px);
margin-top: 24px;
@include breakpoint-xs {
width: 100%;
}
}
.personAvatar {
@ -145,6 +261,12 @@
margin-right: 16px;
}
:global(.dark-mode) .personRole {
@include breakpoint-md {
color: $white-dark !important;
}
}
.personTwitter {
color: #55acee;
text-decoration: none;
@ -166,8 +288,25 @@
svg {
margin-right: 2px;
}
&.personGithub {
color: $black;
p {
color: $black;
&:hover {
color: darken($black, 15);
}
}
svg {
margin-right: 4px;
}
}
}
/* stylelint-disable-next-line */
:global(.dark-mode) .personTwitter p {
color: #55acee;
@ -176,8 +315,24 @@
}
}
:global(.dark-mode) .personTwitter.personGithub p {
color: $white;
&:hover {
color: $off-white-dark;
}
}
:global(.dark-mode) .personTwitter.personGithub p svg path {
fill: $white;
}
.modalFooter {
padding-bottom: 48px;
margin-top: 12px;
text-align: center;
@include breakpoint-md {
text-align: left;
}
}

View File

@ -1,13 +1,17 @@
import dynamic from 'next/dynamic'
import React, { useState } from 'react'
import React, { useState, useRef } from 'react'
import classnames from 'classnames'
import { useDrag } from 'react-use-gesture'
import { Typography } from 'components/typography'
import { ToolTitle } from 'components/tool-title'
import { LazyImage } from 'components/lazy-image'
import { SvgTwitter } from 'components/svgs/svg-twitter'
import { SvgGithub } from 'components/svgs/svg-github'
import styles from './index.module.scss'
import { SvgFavycon } from 'components/svgs/svg-favycon'
import { Button } from 'components/button'
import { isTouchCapable } from 'utils/device'
const Modal = dynamic(() => import('react-modal'))
@ -16,48 +20,86 @@ const people = [
screenName: 'aboutaugusto',
name: 'Augusto Lopes',
role: 'Product Designer',
social: 'twitter',
},
{
screenName: 'rgllm',
name: 'Rogério Moreira',
role: 'Front-End Developer',
social: 'twitter',
},
{
screenName: 'ruisaraiva19',
name: 'Rui Saraiva',
role: 'Full-Stack Developer',
social: 'twitter',
},
{
screenName: 'aNyTh1nGeDuArDo',
name: 'Eduardo Pinto',
role: 'Front-End Developer',
social: 'twitter',
},
{
screenName: 'miguellteixeira',
name: 'Miguel Teixeira',
role: 'Full-Stack Developer',
social: 'github',
},
]
const FavyconInfo = () => {
const FavyconInfo = ({
className,
...props
}: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>) => {
const [isModalOpen, setIsModalOpen] = useState(false)
const modalBodyRef = useRef<HTMLDivElement>(null)
const bind = useDrag(
(state) => {
const {
swipe, // [swipeX, swipeY] 0 if no swipe detected, -1 or 1 otherwise
} = state
const isSwipeDown = swipe[1] === 1
if (isSwipeDown && modalBodyRef.current?.scrollTop === 0) {
setIsModalOpen(false)
}
},
{
enabled: isTouchCapable(),
filterTaps: true,
}
)
return (
<div className={styles.root}>
<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 &amp; 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" className={styles.footer}>
Created by{' '}
<button className={styles.peopleButton} onClick={() => setIsModalOpen(true)}>
{people.length} people
</button>{' '}
on their 2020s worldwide quarantine. Background image from{' '}
<a href="https://unsplash.com/photos/bBiuSdck8tU" target="_blank" rel="noopener noreferrer">
Unsplash
</a>
.
</Typography>
<div className={classnames(styles.root, className)} {...props}>
<div className={styles.bodyMobile}>
<Button variant="modalClose" className={styles.button} onClick={() => setIsModalOpen(true)}>
About
</Button>
</div>
<div className={styles.bodyDesktop}>
<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 &amp; 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" className={styles.footer}>
Created by{' '}
<button className={styles.peopleButton} onClick={() => setIsModalOpen(true)}>
{people.length} people
</button>{' '}
on their 2020s worldwide quarantine. Background image from{' '}
<a href="https://unsplash.com" target="_blank" rel="noopener noreferrer">
Unsplash
</a>
.
</Typography>
</div>
<Modal
ariaHideApp={false}
isOpen={isModalOpen}
@ -66,65 +108,104 @@ const FavyconInfo = () => {
overlayClassName={styles.overlay}
closeTimeoutMS={200}
contentLabel="About Us">
<div className={styles.modalContainer}>
<div className={styles.modalHeader}>
{/* <img src="/favicon.svg" alt="Favycon logo" className={styles.logo} /> */}
<SvgFavycon className={styles.logo} />
<Typography variant="largeTitle" weight="bold" className={styles.title}>
Behind the curtains
</Typography>
<Typography variant="largeBody" weight="medium" className={styles.subTitle}>
For new updates be sure to follow these guys on Twitter.
</Typography>
<div className={styles.modalContainer} {...bind()}>
<div className={styles.modalHeaderMobile}>
<div className={styles.modalHeaderTop}>
<SvgFavycon className={styles.logoMobile} />
<Button variant="modalClose" className={styles.button} onClick={() => setIsModalOpen(false)}>
Close
</Button>
</div>
</div>
<hr className={styles.hr} />
<div className={styles.people}>
{people.map((person) => (
<div key={person.screenName} className={styles.person}>
<div className={styles.personAvatar}>
<LazyImage
src={`/images/people/${person.screenName}@1x.png`}
srcRetina={`/images/people/${person.screenName}@2x.png`}
alt={person.name}
aspectRatio="56/56"
/>
</div>
<div>
<Typography variant="largeBody" weight="semiBold">
{person.name}
</Typography>
<Typography variant="regularBody" weight="medium">
{person.role}
</Typography>
<a
href={`https://twitter.com/${person.screenName}`}
className={styles.personTwitter}
target="_blank"
rel="noopener noreferrer">
<Typography variant="footer" weight="semiBold">
<SvgTwitter /> {person.screenName}
<div ref={modalBodyRef} className={styles.modalBodyScroll}>
<div className={styles.modalHeaderMobile}>
<ToolTitle hideLogo small />
<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 &amp; drop an image and you will then get a downloadable file alongside some documentation on
how to add the favicons.
</Typography>
<hr className={styles.hr} />
<Typography variant="footer" weight="semiBold" color="gray" className={styles.footer}>
Background image from{' '}
<a href="https://unsplash.com" target="_blank" rel="noopener noreferrer">
Unsplash
</a>
.
</Typography>
</div>
<div className={styles.modalHeader}>
<SvgFavycon className={styles.logo} />
<Typography variant="largeTitle" weight="bold" className={styles.title}>
Behind the curtains
</Typography>
<Typography variant="largeBody" weight="medium" className={styles.subTitle}>
For new updates be sure to follow these guys on Twitter.
</Typography>
</div>
<hr className={styles.hr} />
<div className={styles.people}>
{people.map((person) => (
<div key={person.screenName} className={styles.person}>
<div className={styles.personAvatar}>
<LazyImage
src={`/images/people/${person.screenName}@1x.png`}
srcRetina={`/images/people/${person.screenName}@2x.png`}
alt={person.name}
aspectRatio="56/56"
/>
</div>
<div>
<Typography variant="largeBody" weight="semiBold">
{person.name}
</Typography>
</a>
<Typography variant="regularBody" weight="medium" className={styles.personRole}>
{person.role}
</Typography>
{person.social === 'twitter' ? (
<a
href={`https://twitter.com/${person.screenName}`}
className={styles.personTwitter}
target="_blank"
rel="noopener noreferrer">
<Typography variant="footer" weight="semiBold">
<SvgTwitter /> {person.screenName}
</Typography>
</a>
) : (
<a
href={`https://github.com/${person.screenName}`}
className={classnames(styles.personTwitter, styles.personGithub)}
target="_blank"
rel="noopener noreferrer">
<Typography variant="footer" weight="semiBold">
<SvgGithub /> {person.screenName}
</Typography>
</a>
)}
</div>
</div>
</div>
))}
</div>
<hr className={styles.hr} />
<div className={classnames(styles.footer, styles.modalFooter)}>
<Typography variant="footer" weight="semiBold" color="gray">
Project on{' '}
<a
className={styles.repo}
href="https://github.com/ruisaraiva19/favycon"
target="_blank"
rel="noopener noreferrer">
GitHub
</a>
. Have feedback? Send it to{' '}
<a className={styles.contact} href="mailto:hello@favycon.app">
hello@favycon.app
</a>
</Typography>
))}
</div>
<hr className={styles.hr} />
<div className={classnames(styles.footer, styles.modalFooter)}>
<Typography variant="footer" weight="semiBold" color="gray">
Project on{' '}
<a
className={styles.repo}
href="https://github.com/ruisaraiva19/favycon"
target="_blank"
rel="noopener noreferrer">
GitHub
</a>
. Have feedback? Send it to{' '}
<a className={styles.contact} href="mailto:hello@favycon.app">
hello@favycon.app
</a>
</Typography>
</div>
</div>
</div>
</Modal>

View File

@ -8,6 +8,11 @@
width: 100%;
max-width: 540px;
padding: 41px 0 0;
@include breakpoint-md {
max-width: 100%;
padding: 0;
}
}
.background {
@ -28,10 +33,34 @@
bottom: 0;
left: 0;
z-index: 0;
content: '';
content: ' ';
background: rgba($black, 0.16);
border-radius: 8px;
}
@include breakpoint-md {
top: 0;
bottom: 0;
border-radius: 0;
transform: rotate(0);
&::after {
background: rgba($black, 0.56);
border-radius: 0;
}
}
}
.mobileBackground {
@include breakpoint-md-up {
display: none;
}
}
.desktopBackground {
@include breakpoint-md {
display: none;
}
}
.image {
@ -42,11 +71,22 @@
width: 185px;
height: 109px;
opacity: 1;
transition: opacity 0.2s ease-in-out;
transform: translate(100%, -100%);
transition: all 0.2s ease-in-out;
transform: translate(160px, -96px);
&.hide {
pointer-events: none;
visibility: hidden;
opacity: 0;
}
@media screen and (width >= 1280px) {
transform: translate(100%, -100%);
}
@include breakpoint-lg {
pointer-events: none;
visibility: hidden;
opacity: 0;
}
}

File diff suppressed because one or more lines are too long

View File

@ -8,6 +8,11 @@
height: 68px;
padding: 20px 20px 0 20px;
margin: 0 auto;
@include breakpoint-md {
height: 74px;
padding: 26px 26px 0 26px;
}
}
.header {
@ -15,6 +20,16 @@
align-items: center;
justify-content: flex-end;
height: 100%;
@include breakpoint-md {
justify-content: space-between;
}
}
.logo {
@include breakpoint-md-up {
display: none;
}
}
.item {
@ -23,14 +38,30 @@
box-shadow: 0 0 10px rgba($off-white, 0.5);
transition: all 0.2s ease;
@include breakpoint-md {
background: transparent;
box-shadow: none;
}
a {
display: flex;
align-items: center;
justify-content: center;
}
&:first-child {
svg {
width: 48px;
}
}
}
:global(.dark-mode) .item {
background: $darker-black;
box-shadow: 0 0 10px rgba($darker-black, 0.5);
@include breakpoint-md {
background: transparent;
box-shadow: none;
}
}

View File

@ -1,5 +1,7 @@
import React from 'react'
import classnames from 'classnames'
import { DarkModeToggle } from 'components/dark-mode-toggle'
import { SvgFavycon } from 'components/svgs/svg-favycon'
import { Sticky } from 'components/sticky'
import styles from './index.module.scss'
@ -10,6 +12,9 @@ const Header = () => {
<div className={styles.root}>
<div className={styles.container}>
<header className={styles.header}>
<div className={classnames(styles.item, styles.logo)}>
<SvgFavycon />
</div>
<div className={styles.item}>
<DarkModeToggle />
</div>

View File

@ -3,6 +3,11 @@
.root {
position: relative;
width: 100%;
&.stretch {
height: 100%;
padding-bottom: 0 !important;
}
}
.image {

View File

@ -1,6 +1,7 @@
import React, { useRef, useEffect } from 'react'
import { LazyImageFull, ImageState } from 'react-lazy-images'
import objectFitImages from 'object-fit-images'
import classNames from 'classnames'
import styles from './index.module.scss'
@ -10,9 +11,18 @@ type LazyImageProps = {
srcPlaceholder?: string
alt: string
aspectRatio: string
stretch?: boolean
} & React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
const LazyImage = ({ src, srcRetina = src, srcPlaceholder = src, alt, aspectRatio, ...props }: LazyImageProps) => {
const LazyImage = ({
src,
srcRetina = src,
srcPlaceholder = src,
alt,
aspectRatio,
stretch = false,
...props
}: LazyImageProps) => {
const [widthString, heightString] = aspectRatio.split('/')
const width = parseInt(widthString)
const height = parseInt(heightString)
@ -31,9 +41,9 @@ const LazyImage = ({ src, srcRetina = src, srcPlaceholder = src, alt, aspectRati
const loaded = imageState === ImageState.LoadSuccess
return (
<div
className={styles.root}
className={classNames(styles.root, { [styles.stretch]: stretch })}
ref={ref}
style={{ paddingBottom: `${(height / width) * 100 + '%'}` }}
style={{ paddingBottom: `${(height / width) * 100}%` }}
{...props}>
<img
className={styles.placeholder}

View File

@ -0,0 +1,27 @@
import React, { createContext, useContext, useMemo } from 'react'
import PropTypes from 'prop-types'
import useMedia from 'use-media'
type MediaQueryProviderProps = {
children: PropTypes.ReactNodeLike
isMobileDevice?: boolean
}
type ContextProps = {
isMobile: boolean
}
export const MediaQueryContext = createContext<ContextProps>({ isMobile: false })
export const MediaQueryProvider = ({ children, isMobileDevice = false }: MediaQueryProviderProps) => {
const isMobile = useMedia('(max-width: 991px)', isMobileDevice)
const value = useMemo(() => ({ isMobile }), [isMobile])
return <MediaQueryContext.Provider value={value}>{children}</MediaQueryContext.Provider>
}
MediaQueryProvider.propTypes = {
children: PropTypes.node.isRequired,
isMobileDevice: PropTypes.bool,
}
export const useMediaQueryContext = () => useContext(MediaQueryContext)

View File

@ -19,6 +19,7 @@ const SEO = ({ title, description }: SEOProps) => (
<link rel="apple-touch-icon" sizes="144x144" href="/favicon-144x144.png" />
<link rel="apple-touch-icon" sizes="152x152" href="/favicon-152x152.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/favicon-180x180.png" />
<meta name="apple-mobile-web-app-title" content="Favycon" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
@ -32,16 +33,11 @@ const SEO = ({ title, description }: SEOProps) => (
<meta property="og:type" content="website" />
<meta property="og:site_name" content={title} />
<meta property="og:url" content="https://favycon.app" />
<meta property="og:image" content="https://favycon.app/share.png?v2" />
<meta property="og:image" content="https://favycon.app/share.png?v3" />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content={description} />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:url" content="https://favycon.app" />
<meta name="twitter:image" content="https://favycon.app/share.png?v2" />
<meta name="twitter:card" content="summary_large_image" />
</Head>
)

View File

@ -1,45 +0,0 @@
import React from 'react'
import { uniqueId } from 'utils/ids'
const SvgAppycon: React.FC<JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>> = (props) => {
const idPrefix = uniqueId('svg-')
return (
<svg width={64} height={64} viewBox="0 0 64 64" aria-labelledby={`${idPrefix}-title`} {...props}>
<title id={`${idPrefix}-title`}>Appycon logo</title>
<defs>
<linearGradient x1="50%" y1="99.731%" x2="50%" y2=".44%" id={`${idPrefix}-c`}>
<stop stopColor="#FFBB45" offset="0%" />
<stop stopColor="#D53838" offset="100%" />
</linearGradient>
<filter x="-6.3%" y="-4.5%" width="112.5%" height="112.5%" filterUnits="objectBoundingBox" id={`${idPrefix}-a`}>
<feOffset dy={1} in="SourceAlpha" result="shadowOffsetOuter1" />
<feGaussianBlur stdDeviation={1} in="shadowOffsetOuter1" result="shadowBlurOuter1" />
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0" in="shadowBlurOuter1" />
</filter>
<path
d="M29.669 4h4.662c6.766 0 10.83 1.021 14.414 2.938a19.99 19.99 0 018.317 8.317C58.979 18.84 60 22.903 60 29.669v4.662c0 6.766-1.021 10.83-2.938 14.414a19.99 19.99 0 01-8.317 8.317C45.16 58.979 41.097 60 34.331 60H29.67c-6.766 0-10.83-1.021-14.414-2.938a19.99 19.99 0 01-8.317-8.317C5.021 45.16 4 41.097 4 34.331V29.67c0-6.766 1.021-10.83 2.938-14.414a19.99 19.99 0 018.317-8.317C18.84 5.021 22.903 4 29.669 4z"
id={`${idPrefix}-b`}
/>
</defs>
<g fill="none" fillRule="evenodd">
<g transform="rotate(-8 32 32)">
<use fill="#000" filter={`url(${idPrefix}-a)`} xlinkHref={`${idPrefix}-b`} />
<use fill={`url(${idPrefix}-b)`} xlinkHref={`${idPrefix}-b`} />
</g>
<path
d="M25.795 4.597l4.617-.649c6.7-.942 10.865-.496 14.682.904a19.99 19.99 0 019.393 7.078c2.398 3.283 3.974 7.165 4.916 13.865l.649 4.617c.942 6.7.496 10.865-.904 14.682a19.99 19.99 0 01-7.078 9.393c-3.283 2.398-7.165 3.974-13.865 4.916l-4.617.649c-6.7.942-10.865.496-14.682-.904a19.99 19.99 0 01-9.393-7.078c-2.398-3.283-3.974-7.165-4.916-13.865l-.649-4.617c-.942-6.7-.496-10.865.904-14.682a19.99 19.99 0 017.078-9.393c3.283-2.398 7.165-3.974 13.865-4.916zm5.313 4.302l-4.618.65c-5.587.785-8.831 1.972-11.612 4.002a14.992 14.992 0 00-5.332 7.076C8.398 23.758 8.13 27.09 8.83 32.375l.07.517.65 4.618c.785 5.587 1.972 8.831 4.002 11.612a14.992 14.992 0 007.076 5.332c3.131 1.148 6.463 1.416 11.748.717l.517-.07 4.618-.65c5.587-.785 8.831-1.972 11.612-4.002a14.992 14.992 0 005.332-7.076c1.148-3.131 1.416-6.463.717-11.748l-.07-.517-.65-4.618c-.785-5.587-1.972-8.831-4.002-11.612a14.992 14.992 0 00-7.076-5.332C40.242 8.398 36.91 8.13 31.625 8.83l-.517.07z"
fillOpacity={0.48}
fill="#FFF"
fillRule="nonzero"
/>
<path
d="M21.532 41.283l.611-4.03 6.806-.957 1.698 3.706 3.516-.495-8.134-17.79-5.098.716-2.915 19.344 3.516-.494zm5.977-8.125l-4.872.685 1.266-8.488 3.606 7.803zm13.425 11.71l4.833-21.192-3.416.48-2.21 9.962-4.896-8.963-3.541.497 7.417 13.053-1.35 6.607 3.163-.445z"
fill="#FFF"
fillRule="nonzero"
/>
</g>
</svg>
)
}
export { SvgAppycon }

View File

@ -1,16 +0,0 @@
import React from 'react'
import useDarkMode from 'use-dark-mode'
import { uniqueId } from 'utils/ids'
const SvgArrowDown: React.FC<JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>> = (props) => {
const { value: isDark } = useDarkMode(false)
const idPrefix = uniqueId('svg-')
return (
<svg width={16} height={16} viewBox="0 0 16 16" aria-labelledby={`${idPrefix}-title`} {...props}>
<title id={`${idPrefix}-title`}>Arrow down</title>
<path d="M3 6l5 5 5-5" stroke={isDark ? '#5a5b66' : '#ccc'} strokeWidth={2} fill="none" strokeLinejoin="round" />
</svg>
)
}
export { SvgArrowDown }

View File

@ -1,8 +1,9 @@
@import '../../styles/variables.scss';
.unchecked {
fill: #f7f7f8;
fill: #f7f7f7;
stroke: #ccc;
stroke-width: 1.5px;
transition: all 100ms ease-in-out;
&.disabled {
@ -13,11 +14,12 @@
:global(.dark-mode) .unchecked {
fill: $dark-grey;
stroke: $grey-dark;
stroke: $darker-black;
stroke-width: 1.5px;
&.disabled {
fill: $dark-black;
stroke: $dark-grey;
stroke: $darker-black;
}
}

View File

@ -3,6 +3,10 @@
.rect {
stroke: #ccc;
transition: stroke 0.2s ease;
@include breakpoint-md {
stroke-width: 0;
}
}
:global(.dark-mode) .rect {

View File

@ -1,10 +1,9 @@
import React from 'react'
import { uniqueId } from 'utils/ids'
import styles from './svg-favycon.module.scss'
const SvgFavycon: React.FC<JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>> = (props) => {
const idPrefix = uniqueId('svg-')
const idPrefix = 'svg-favycon-'
return (
<svg width={64} height={64} viewBox="0 0 64 64" aria-labelledby={`${idPrefix}-title`} {...props}>
<title id={`${idPrefix}-title`}>Favycon logo</title>

View File

@ -0,0 +1,12 @@
import React from 'react'
const SvgGithub: React.FC<JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>> = (props) => (
<svg height={18} viewBox="0 0 16 16" width={18} {...props}>
<path
fillRule="evenodd"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"
/>
</svg>
)
export { SvgGithub }

View File

@ -1,11 +1,53 @@
@import '../../styles/variables.scss';
.circle {
opacity: 0;
fill: $white;
transition: all 0.2s ease;
@include breakpoint-md {
opacity: 0.1;
}
}
.rect {
fill: #ccc;
transition: all 0.2s ease;
&:nth-of-type(1) {
y: 20px;
height: 48px;
}
&:nth-of-type(2) {
x: 20px;
width: 48px;
}
&.active {
fill: $link;
@include breakpoint-md-up {
fill: $link;
}
&:nth-of-type(1) {
/* mouse, touch pad */
@media (hover: hover) and (pointer: fine) {
y: 16px;
height: 56px;
}
}
&:nth-of-type(2) {
/* mouse, touch pad */
@media (hover: hover) and (pointer: fine) {
x: 16px;
width: 56px;
}
}
}
@include breakpoint-md {
fill: $off-white-dark;
}
}
@ -15,4 +57,8 @@
&.active {
fill: $off-white-dark;
}
@include breakpoint-md {
fill: $off-white-dark;
}
}

View File

@ -1,6 +1,5 @@
import React from 'react'
import classnames from 'classnames'
import { uniqueId } from 'utils/ids'
import styles from './svg-image-upload.module.scss'
@ -12,24 +11,13 @@ const SvgImageUpload: React.FC<JSX.IntrinsicAttributes & React.SVGProps<SVGSVGEl
active = false,
...props
}) => {
const idPrefix = uniqueId('svg-')
const idPrefix = 'svg-image-upload-'
return (
<svg width={88} height={88} viewBox="0 0 88 88" aria-labelledby={`${idPrefix}-title`} {...props}>
<title id={`${idPrefix}-title`}>Image Upload</title>
<rect
x={43}
y={active ? 16 : 20}
width={2}
height={active ? 56 : 48}
className={classnames(styles.rect, { [styles.active]: active })}
/>
<rect
x={active ? 16 : 20}
y={43}
width={active ? 56 : 48}
height={2}
className={classnames(styles.rect, { [styles.active]: active })}
/>
<circle r={44} cx={44} cy={44} className={styles.circle} />
<rect x={43} y={20} width={2} height={48} className={classnames(styles.rect, { [styles.active]: active })} />
<rect x={20} y={43} width={48} height={2} className={classnames(styles.rect, { [styles.active]: active })} />
</svg>
)
}

View File

@ -1,10 +1,9 @@
import React from 'react'
import { uniqueId } from 'utils/ids'
import styles from './svg-info.module.scss'
const SvgInfo: React.FC<JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>> = (props) => {
const idPrefix = uniqueId('svg-')
const idPrefix = 'svg-info-'
return (
<svg width={20} height={20} viewBox="0 0 20 20" aria-labelledby={`${idPrefix}-title`} {...props}>
<title id={`${idPrefix}-title`}>Info</title>

View File

@ -1,8 +1,7 @@
import React from 'react'
import { uniqueId } from 'utils/ids'
const SvgMoon: React.FC<JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>> = (props) => {
const idPrefix = uniqueId('svg-')
const idPrefix = 'svg-moon-'
return (
<svg width={32} height={32} viewBox="0 0 32 32" aria-labelledby={`${idPrefix}-title`} {...props}>
<title id={`${idPrefix}-title`}>Activate dark mode</title>

View File

@ -1,8 +1,7 @@
import React from 'react'
import { uniqueId } from 'utils/ids'
const SvgSun: React.FC<JSX.IntrinsicAttributes & React.SVGProps<SVGSVGElement>> = (props) => {
const idPrefix = uniqueId('svg-')
const idPrefix = 'svg-sun-'
return (
<svg width={32} height={32} viewBox="0 0 32 32" aria-labelledby={`${idPrefix}-title`} {...props}>
<title id={`${idPrefix}-title`}>Activate light mode</title>

View File

@ -4,10 +4,15 @@ import { SvgFavycon } from 'components/svgs/svg-favycon'
import styles from './index.module.scss'
const ToolTitle = () => (
type ToolTitleProps = {
hideLogo?: boolean
small?: boolean
}
const ToolTitle = ({ hideLogo = false, small = false }: ToolTitleProps) => (
<div className={styles.root}>
<SvgFavycon />
<Typography variant="h1" weight="extraBold" color="black">
{!hideLogo && <SvgFavycon />}
<Typography variant={small ? 'h2' : 'h1'} weight="extraBold" color="black">
Favycon
</Typography>
<Typography variant="superscript" tag="span" weight="bold" color="gray">

View File

@ -9,6 +9,11 @@
line-height: $line-height-h1;
}
.h2 {
font-size: $font-size-extra-large-title;
line-height: $line-height-extra-large-title;
}
.smallBody {
font-size: $font-size-small-body;
line-height: $line-height-small-body;
@ -17,6 +22,10 @@
.regularBody {
font-size: $font-size-regular-body;
line-height: $line-height-regular-body;
@include breakpoint-md {
font-size: $font-size-medium-body;
}
}
.mediumBody {
@ -33,6 +42,10 @@
font-size: $font-size-footer;
line-height: $line-height-footer;
@include breakpoint-md {
font-size: $font-size-small-body;
}
a {
color: inherit;
@ -52,6 +65,10 @@
.title {
font-size: $font-size-title;
line-height: $line-height-title;
@include breakpoint-md {
font-size: $font-size-medium-body;
}
}
.largeTitle {
@ -59,6 +76,11 @@
line-height: $line-height-large-title;
}
.extraLargeTitle {
font-size: $font-size-extra-large-title;
line-height: $line-height-extra-large-title;
}
.regular {
font-weight: 400;
}
@ -101,7 +123,7 @@
transition: color 0.2s ease;
}
:global(.dark-mode) .white {
:global(.dark-mode) .white:not(.colorImmutable) {
color: $black;
}
@ -115,5 +137,5 @@
}
:global(.dark-mode) .muted {
color: $grey;
color: $grey-dark;
}

View File

@ -8,6 +8,7 @@ type TypographyProps = {
children: PropTypes.ReactNodeLike
variant:
| 'h1'
| 'h2'
| 'smallBody'
| 'regularBody'
| 'mediumBody'
@ -16,22 +17,25 @@ type TypographyProps = {
| 'superscript'
| 'title'
| 'largeTitle'
| 'extraLargeTitle'
weight: 'regular' | 'medium' | 'semiBold' | 'bold' | 'extraBold'
color: 'black' | 'gray' | 'white' | 'green'
colorImmutable?: boolean
tag?: string
muted?: boolean
} & React.DetailedHTMLProps<React.HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
const Typography = ({ children, variant, weight, color, tag, muted, ...props }: TypographyProps) => {
const Typography = ({ children, variant, weight, color, colorImmutable, tag, muted, ...props }: TypographyProps) => {
const className = classNames(
styles.root,
styles[variant],
styles[color],
styles[weight],
{ [styles.muted]: muted },
{ [styles.colorImmutable]: colorImmutable },
props.className
)
const componentType = ['h1'].includes(variant) ? variant : 'p'
const componentType = ['h1', 'h2'].includes(variant) ? variant : 'p'
return React.createElement(tag || componentType, { ...props, className }, children)
}
@ -40,6 +44,7 @@ Typography.defaultProps = {
variant: 'p',
weight: 'regular',
color: 'black',
colorImmutable: false,
}
export { Typography }

5
cypress.json Normal file
View File

@ -0,0 +1,5 @@
{
"baseUrl": "http://localhost:3000",
"viewportWidth": 1280,
"viewportHeight": 720
}

6
cypress/.eslintrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"extends": ["plugin:cypress/recommended"],
"rules": {
"cypress/no-unnecessary-waiting": "off"
}
}

View File

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@ -0,0 +1,7 @@
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="white" d="M140 0H20C8.9543 0 0 8.9543 0 20V140C0 151.046 8.9543 160 20 160H140C151.046 160 160 151.046 160 140V20C160 8.9543 151.046 0 140 0Z"/>
<path fill="#F6A044" fill-rule="evenodd" clip-rule="evenodd" d="M134 26C134 32.24 131.47 37.89 127.38 41.98C123.29 46.07 117.64 48.6 111.4 48.6H48.6V26H134Z"/>
<path fill="#4C3A38" fill-rule="evenodd" clip-rule="evenodd" d="M48.6 26V48.6H26C26 36.12 36.12 26 48.6 26Z"/>
<path fill="#4C5ED5" fill-rule="evenodd" clip-rule="evenodd" d="M48.6 48.6V111.4C48.6 123.88 38.48 134 26 134V48.6H48.6Z"/>
<path fill="#4C5ED5" fill-rule="evenodd" clip-rule="evenodd" d="M80.34 93.04L71.52 97.68C70.55 98.19 69.36 97.82 68.85 96.85C68.65 96.46 68.58 96.02 68.65 95.6L70.33 85.78C70.44 85.14 70.23 84.48 69.76 84.03L62.63 77.08C61.85 76.32 61.83 75.07 62.59 74.28C62.89 73.97 63.29 73.77 63.72 73.7L73.58 72.27C74.22 72.18 74.78 71.77 75.07 71.19L79.48 62.26C79.96 61.28 81.15 60.88 82.13 61.36C82.52 61.55 82.84 61.87 83.03 62.26L87.44 71.19C87.73 71.77 88.29 72.18 88.93 72.27L98.79 73.7C99.87 73.86 100.62 74.86 100.46 75.94C100.4 76.37 100.19 76.77 99.88 77.07L92.75 84.02C92.28 84.47 92.07 85.13 92.18 85.77L93.86 95.59C94.04 96.67 93.32 97.69 92.24 97.87C91.81 97.94 91.37 97.87 90.99 97.67L82.17 93.03C81.6 92.74 80.91 92.74 80.34 93.04Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,98 @@
/// <reference types="cypress" />
describe('Drag & Drop', () => {
describe('Using drag events', () => {
beforeEach(() => {
cy.viewport(1280, 720)
cy.visit('/')
cy.wait(200)
})
function dropFile(fixture) {
cy.get('[data-cy="drag-and-drop"]').attachFile(fixture, {
subjectType: 'drag-n-drop',
})
cy.wait(20)
}
it('Drop 310x310 PNG file', () => {
dropFile('images/favicon-310x310.png')
cy.get('[data-cy="preview-title"]').should('have.text', 'Good to go!')
cy.get('[data-cy="preview-square-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-png-svg-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-310px-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-pwa-compatible"]').should('have.attr', 'disabled')
})
it('Drop 640x640 PNG file', () => {
dropFile('images/favicon-640x640.png')
cy.get('[data-cy="preview-title"]').should('have.text', 'Good to go!')
cy.get('[data-cy="preview-square-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-png-svg-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-310px-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-pwa-compatible"]').should('not.have.attr', 'disabled')
})
it('Drop 160x160 SVG file', () => {
dropFile('images/favicon.svg')
cy.get('[data-cy="preview-title"]').should('have.text', 'Good to go!')
cy.get('[data-cy="preview-square-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-png-svg-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-310px-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-pwa-compatible"]').should('not.have.attr', 'disabled')
})
it('Drop 150x150 PNG file', () => {
dropFile('images/favicon-150x150.png')
cy.get('[data-cy="preview-title"]').should('have.text', 'Your image should be higher than 310px')
cy.get('[data-cy="preview-square-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-png-svg-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-310px-image"] svg path').should('have.class', 'svgError')
cy.get('[data-cy="preview-pwa-compatible"]').should('have.attr', 'disabled')
})
it('Drop 160x160 JPG file', () => {
dropFile('images/favicon-160x160.jpg')
cy.get('[data-cy="preview-title"]').should('have.text', 'Your image has a few problems')
cy.get('[data-cy="preview-square-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-png-svg-image"] svg path').should('have.class', 'svgError')
cy.get('[data-cy="preview-310px-image"] svg path').should('have.class', 'svgError')
cy.get('[data-cy="preview-pwa-compatible"]').should('have.attr', 'disabled')
})
it('Drop 320x320 JPG file', () => {
dropFile('images/favicon-320x320.jpg')
cy.get('[data-cy="preview-title"]').should('have.text', 'Your image should be in PNG or SVG')
cy.get('[data-cy="preview-square-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-png-svg-image"] svg path').should('have.class', 'svgError')
cy.get('[data-cy="preview-310px-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-pwa-compatible"]').should('have.attr', 'disabled')
})
it('Drop 360x320 PNG file', () => {
dropFile('images/favicon-360x320.png')
cy.get('[data-cy="preview-title"]').should('have.text', 'Your image should be squared')
cy.get('[data-cy="preview-square-image"] svg path').should('have.class', 'svgError')
cy.get('[data-cy="preview-png-svg-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-310px-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-pwa-compatible"]').should('have.attr', 'disabled')
})
it('Drop 360x320 JPG file', () => {
dropFile('images/favicon-360x320.jpg')
cy.get('[data-cy="preview-title"]').should('have.text', 'Your image has a few problems')
cy.get('[data-cy="preview-square-image"] svg path').should('have.class', 'svgError')
cy.get('[data-cy="preview-png-svg-image"] svg path').should('have.class', 'svgError')
cy.get('[data-cy="preview-310px-image"] svg path').should('have.class', 'svgCheck')
cy.get('[data-cy="preview-pwa-compatible"]').should('have.attr', 'disabled')
})
})
})

View File

@ -0,0 +1,7 @@
/// <reference types="cypress" />
describe('My First Test', () => {
it('Visits the Favycon', () => {
cy.visit('/')
})
})

21
cypress/plugins/index.js Normal file
View File

@ -0,0 +1,21 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
module.exports = () => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View File

@ -0,0 +1,27 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
import 'cypress-file-upload'

20
cypress/support/index.js Normal file
View File

@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

11
hooks/use-toggle.ts Normal file
View File

@ -0,0 +1,11 @@
import React from 'react'
export const useToggle = (initialValue = false): [boolean, () => void] => {
const [value, setValue] = React.useState(initialValue)
const toggle = React.useCallback(() => {
setValue((v) => !v)
}, [])
return [value, toggle]
}

View File

@ -1,7 +1,5 @@
module.exports = {
'*.{ts,tsx}': () => 'tsc -p tsconfig.json --noEmit',
'*.tsx': ['eslint --fix', 'stylelint --fix'],
'*.{js,ts}': ['eslint --fix'],
'*.{js,jsx,ts,tsx}': ['eslint --fix'],
'*.json': ['prettier --write'],
'*.{css,scss}': ['stylelint --fix'],
}

View File

@ -1,28 +1,34 @@
require('dotenv').config()
const withPrefresh = require('@prefresh/next')
const isProduction = process.env.NODE_ENV === 'production'
module.exports = withPrefresh({
experimental: {
modern: true,
polyfillsOptimization: true,
},
module.exports = {
webpack(config, { dev, isServer }) {
// Move Preact into the framework chunk instead of duplicating in routes:
const splitChunks = config.optimization && config.optimization.splitChunks
if (splitChunks) {
const { cacheGroups } = splitChunks
const preactModules = /[\\/]node_modules[\\/](preact|preact-render-to-string|preact-context-provider)[\\/]/
const test = /[\\/]node_modules[\\/](preact|preact-render-to-string|preact-context-provider)[\\/]/
if (cacheGroups.framework) {
cacheGroups.preact = { ...cacheGroups.framework, test: preactModules }
cacheGroups.preact = { ...cacheGroups.framework, test }
// if you want to merge the 2 small commons+framework chunks:
cacheGroups.commons.name = 'framework'
} else {
cacheGroups.preact = {
name: 'commons',
chunks: 'all',
test: preactModules,
}
}
}
config.resolve.alias['react'] = 'preact/compat'
config.resolve.alias['react-dom'] = 'preact/compat'
config.resolve.alias['react-ssr-prepass'] = 'preact-ssr-prepass'
if (isServer) {
// mark `preact` stuffs as external for server bundle to prevent duplicate copies of preact
config.externals.push(/^(preact|preact-render-to-string|preact-context-provider)([\\/]|$)/)
}
// Install webpack aliases:
const aliases = config.resolve.alias || (config.resolve.alias = {})
aliases['react'] = 'preact/compat'
aliases['react-dom'] = 'preact/compat'
aliases['react-ssr-prepass'] = 'preact-ssr-prepass'
// inject Preact DevTools
if (dev && !isServer) {
@ -37,5 +43,5 @@ module.exports = {
return config
},
poweredByHeader: !isProduction,
}
poweredByHeader: false,
})

View File

@ -18,7 +18,7 @@
"author": {
"name": "Rui Saraiva",
"email": "ruisaraiva19@gmail.com",
"url": "https://ruisaraiva.com/"
"url": "https://ruisaraiva.dev/"
},
"license": "MIT",
"bugs": {
@ -26,104 +26,116 @@
},
"homepage": "https://github.com/ruisaraiva19/favycon#readme",
"engines": {
"node": ">= 10.x"
"node": ">= 10.13"
},
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "npm-run-all --parallel lint:*",
"lint:types": "tsc",
"lint:scripts": "eslint './**/*.tsx' './**/*.ts' './**/*.js'",
"lint:css": "stylelint './**/*.css' './**/*.scss'",
"lint:scripts": "eslint --ext js,jsx,ts,tsx .",
"lint:styles": "stylelint ./**/*.{css,scss}",
"storybook": "start-storybook -s ./public -p 6006",
"storybook:build": "build-storybook -s ./public",
"commit": "git-cz",
"chromatic": "chromatic --build-script-name=storybook:build"
"e2e": "start-test dev 3000 cy:open",
"cy:open": "cypress open"
},
"dependencies": {
"@fiahfy/ico-convert": "0.0.7",
"adm-zip": "0.4.14",
"@prefresh/next": "1.1.0",
"adm-zip": "0.4.16",
"classnames": "2.2.6",
"multer": "1.4.2",
"next": "9.4.0",
"next": "9.5.3",
"nprogress": "0.2.0",
"object-fit-images": "3.2.4",
"preact": "10.4.1",
"preact-render-to-string": "5.1.8",
"preact-ssr-prepass": "1.0.1",
"prismjs": "1.20.0",
"preact": "10.4.8",
"preact-render-to-string": "5.1.10",
"preact-ssr-prepass": "1.1.1",
"prismjs": "1.21.0",
"prop-types": "15.7.2",
"react": "16.13.1",
"react-clipboard.js": "2.0.16",
"react-dom": "16.13.1",
"react-dropzone": "11.0.1",
"react-dropzone": "11.0.3",
"react-lazy-images": "1.1.0",
"react-modal": "3.11.2",
"react-transition-group": "4.4.1",
"sharp": "0.25.2",
"use-dark-mode": "2.3.1"
"react-use-gesture": "7.0.16",
"sharp": "0.25.0",
"use-dark-mode": "2.3.1",
"use-media": "1.4.0"
},
"devDependencies": {
"@babel/core": "7.9.6",
"@commitlint/cli": "8.3.5",
"@commitlint/config-conventional": "8.3.4",
"@storybook/addon-actions": "5.3.18",
"@storybook/addon-backgrounds": "5.3.18",
"@babel/core": "7.11.6",
"@commitlint/cli": "9.1.2",
"@commitlint/config-conventional": "9.1.2",
"@storybook/addon-actions": "5.3.21",
"@storybook/addon-backgrounds": "5.3.21",
"@storybook/addon-console": "1.2.1",
"@storybook/addon-knobs": "5.3.18",
"@storybook/addon-links": "5.3.18",
"@storybook/addon-storysource": "5.3.18",
"@storybook/addon-viewport": "5.3.18",
"@storybook/addons": "5.3.18",
"@storybook/react": "5.3.18",
"@storybook/source-loader": "5.3.18",
"@storybook/addon-knobs": "5.3.21",
"@storybook/addon-links": "5.3.21",
"@storybook/addon-storysource": "5.3.21",
"@storybook/addon-viewport": "5.3.21",
"@storybook/addons": "5.3.21",
"@storybook/react": "5.3.21",
"@storybook/source-loader": "5.3.21",
"@types/adm-zip": "0.4.33",
"@types/classnames": "2.2.10",
"@types/multer": "1.4.3",
"@types/node": "13.13.5",
"@types/multer": "1.4.4",
"@types/node": "14.6.4",
"@types/nprogress": "0.2.0",
"@types/object-fit-images": "3.2.0",
"@types/prismjs": "1.16.0",
"@types/react": "16.9.35",
"@types/react-modal": "3.10.5",
"@types/react-transition-group": "4.2.4",
"@types/sharp": "0.25.0",
"@typescript-eslint/eslint-plugin": "2.32.0",
"@typescript-eslint/parser": "2.32.0",
"all-contributors-cli": "6.14.2",
"@types/prismjs": "1.16.1",
"@types/react": "16.9.49",
"@types/react-modal": "3.10.6",
"@types/react-transition-group": "4.4.0",
"@types/sharp": "0.25.1",
"@typescript-eslint/eslint-plugin": "4.1.0",
"@typescript-eslint/parser": "4.1.0",
"all-contributors-cli": "6.17.2",
"babel-eslint": "10.1.0",
"babel-loader": "8.1.0",
"babel-plugin-prismjs": "2.0.1",
"babel-preset-react-app": "9.1.2",
"commitizen": "4.1.2",
"cz-conventional-changelog": "3.2.0",
"commitizen": "4.2.1",
"cypress": "5.1.0",
"cypress-file-upload": "4.1.1",
"cz-conventional-changelog": "3.3.0",
"dotenv": "8.2.0",
"eslint": "6.8.0",
"eslint": "7.8.1",
"eslint-config-prettier": "6.11.0",
"eslint-config-react": "1.1.7",
"eslint-config-react-app": "5.2.1",
"eslint-plugin-flowtype": "4.7.0",
"eslint-plugin-import": "2.20.2",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-prettier": "3.1.3",
"eslint-plugin-react": "7.19.0",
"eslint-plugin-react-hooks": "2.5.1",
"husky": "4.2.5",
"lint-staged": "10.2.2",
"eslint-plugin-cypress": "2.11.1",
"eslint-plugin-flowtype": "5.2.0",
"eslint-plugin-import": "2.22.0",
"eslint-plugin-jsx-a11y": "6.3.1",
"eslint-plugin-prettier": "3.1.4",
"eslint-plugin-react": "7.20.6",
"eslint-plugin-react-hooks": "4.1.0",
"husky": "4.3.0",
"lint-staged": "10.3.0",
"npm-run-all": "4.1.5",
"prettier": "2.0.5",
"regenerator-runtime": "0.13.5",
"sass": "1.26.5",
"sass-loader": "8.0.2",
"semantic-release": "17.0.7",
"stylelint": "13.3.3",
"postcss-100vh-fix": "0.1.1",
"postcss-flexbugs-fixes": "4.2.1",
"postcss-preset-env": "6.7.0",
"prettier": "2.1.1",
"react-refresh": "0.8.3",
"regenerator-runtime": "0.13.7",
"sass": "1.26.10",
"sass-loader": "9.0.3",
"semantic-release": "17.1.1",
"start-server-and-test": "1.11.3",
"stylelint": "13.7.0",
"stylelint-config-css-modules": "2.2.0",
"stylelint-config-prettier": "8.0.1",
"stylelint-config-recess-order": "2.0.4",
"stylelint-config-prettier": "8.0.2",
"stylelint-config-recess-order": "2.1.0",
"stylelint-config-standard": "20.0.0",
"stylelint-prettier": "1.1.2",
"typescript": "3.8.3",
"stylelint-scss": "3.18.0",
"typescript": "3.9.7",
"webpack-notifier": "1.8.0"
},
"config": {

View File

@ -1,10 +1,15 @@
import React from 'react'
import { AppProps } from 'next/app'
import { MediaQueryProvider } from 'components/media-query-provider'
import '../styles/main.scss'
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
return (
<MediaQueryProvider>
<Component {...pageProps} />
</MediaQueryProvider>
)
}
export default MyApp

View File

@ -1,11 +1,27 @@
import React from 'react'
import Document, { Html, Head, Main, NextScript } from 'next/document'
const isProd = process.env.NODE_ENV === 'production'
class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head />
<Head>
{isProd && <script async src="https://www.googletagmanager.com/gtag/js?id=UA-171039315-1" />}
{isProd && (
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'UA-171039315-1', { 'anonymize_ip': true });
`,
}}
/>
)}
</Head>
<body className="preload">
<script src="/js/noflash.js" />
<Main />

View File

@ -104,6 +104,6 @@ export default async (req: MulterApiRequest, res: NextApiResponse) => {
break
default:
res.setHeader('Allow', ['POST'])
res.status(405).end(`Method ${req.method} Not Allowed`)
res.status(405).end(`Method ${req.method || ''} Not Allowed`)
}
}

View File

@ -6,6 +6,7 @@
}
.container {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
@ -14,4 +15,10 @@
min-height: 100vh;
padding: 52px 20px;
margin: 0 auto;
@include breakpoint-md {
align-items: stretch;
max-width: 100%;
padding: 0;
}
}

View File

@ -29,16 +29,20 @@ const Home: NextPage = () => {
setError(error)
}
const onGenerate = async (file: File, pwa: boolean) => {
const onGenerate = async (file: File, pwa: boolean, dark: boolean) => {
try {
NProgress.start()
const formData = new FormData()
formData.append('icon', file)
formData.append('pwa', pwa ? '1' : '0')
formData.append('dark', dark ? '1' : '0')
const response = await fetch('/api/favycon', {
method: 'POST',
body: formData,
})
if (response.status >= 300) {
throw new Error(await response.text())
}
return await response.arrayBuffer()
} catch (error) {
throw error
@ -55,12 +59,12 @@ const Home: NextPage = () => {
/>
<main className={styles.main}>
<div className={styles.container}>
<FavyconInfo />
<FavyconInfo className={styles.info} />
<FavyconWizard showDndImage={!file}>
<DragAndDrop key={fileCounter} onFile={setFile} onGenerate={onGenerate} onError={onError} />
</FavyconWizard>
</div>
<FavyconError key={errorCounter} error={error} clearError={() => onError('')} />
<FavyconError key={errorCounter} error={error} clearError={() => setError('')} />
</main>
</BaseLayout>
)

15
postcss.config.js Normal file
View File

@ -0,0 +1,15 @@
module.exports = {
plugins: {
'postcss-100vh-fix': {},
'postcss-flexbugs-fixes': {},
'postcss-preset-env': {
autoprefixer: {
flexbox: 'no-2009',
},
stage: 3,
features: {
'custom-properties': false,
},
},
},
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 676 B

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 516 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 782 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 981 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 993 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 KiB

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -1,30 +1,23 @@
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="favicon-bg" fill="#2E3A47" fill-rule="evenodd" clip-rule="evenodd" d="M13.1834 5.55412L42.8091 1.39074C47.5779 0.724999 49.3761 0.97341 51.2538 1.65903C53.1315 2.34464 54.6912 3.52708 55.8635 5.13679C57.0458 6.74649 57.781 8.40588 58.4466 13.1754L62.6093 42.8059C63.275 47.5754 63.0266 49.3739 62.3411 51.2519C61.6556 53.1299 60.4733 54.6899 58.8639 55.8624C57.2544 57.0448 55.5953 57.7801 50.8266 58.4459L21.2008 62.6093C16.4321 63.275 14.6339 63.0266 12.7562 62.341C10.8785 61.6554 9.3187 60.4729 8.14639 58.8632C6.96414 57.2535 6.22896 55.5941 5.56332 50.8246L1.39068 21.1941C0.725043 16.4246 0.973415 14.6261 1.65892 12.7481C2.34443 10.8701 3.52668 9.3101 5.13612 8.1376C6.75551 6.96509 8.41463 6.21986 13.1834 5.55412Z" />
<path id="favicon-fg" fill="#FFFFFF" fill-rule="evenodd" clip-rule="evenodd" d="M13.8192 7.28306L42.6701 3.22899C46.644 2.67254 48.1442 2.88121 49.7039 3.45752C51.2637 4.03384 52.5652 5.00761 53.5487 6.35897C54.5323 7.70039 55.1482 9.08156 55.7046 13.0661L59.311 38.7419C59.9766 43.5114 59.7282 45.3099 59.0427 47.1879C58.3572 49.0659 57.175 50.6259 55.5655 51.7984C53.9561 52.9808 52.2969 53.7161 47.5282 54.3819L23.2474 57.7901C18.4787 58.4558 16.6804 58.2074 14.8028 57.5218C12.9251 56.8362 11.3653 55.6537 10.193 54.044C9.01073 52.4343 8.27555 50.7749 7.60991 46.0054L4.00355 20.3197C3.4472 16.3451 3.65583 14.8447 4.23206 13.2847C4.80828 11.7247 5.7819 10.423 7.13304 9.43927C8.46431 8.45556 9.84526 7.8395 13.8192 7.28306Z" />
<path id="favicon-top" fill="#F6A044" fill-rule="evenodd" clip-rule="evenodd" d="M40.3155 19.9322L20.7041 22.6846L19.7006 15.5502L46.4453 11.7942C47.0017 15.729 44.2596 19.3757 40.3155 19.9322Z" />
<path id="favicon-corner" fill="#493B39" fill-rule="evenodd" clip-rule="evenodd" d="M20.7041 22.6846L13.5708 23.6882C13.0145 19.7533 15.7565 16.1066 19.7006 15.5502L20.7041 22.6846Z" />
<path id="favicon-left" fill="#4C5ED5" fill-rule="evenodd" clip-rule="evenodd" d="M17.207 49.5528L13.5708 23.6882L20.7041 22.6846L23.3368 41.4148C23.8832 45.3496 21.1412 48.9963 17.207 49.5528Z" />
<path id="favicon-star" fill="#4C5ED5" fill-rule="evenodd" clip-rule="evenodd" d="M32.2384 35.761L29.6653 37.6191C29.3871 37.8178 28.9897 37.7582 28.7811 37.48C28.7016 37.3707 28.6619 37.2315 28.6619 37.0924L28.7612 33.9227C28.7712 33.714 28.6718 33.5153 28.5029 33.3961L25.9397 31.5181C25.6616 31.3094 25.602 30.9219 25.8007 30.6337C25.8801 30.5244 25.9994 30.4449 26.1285 30.4052L29.1785 29.5109C29.3772 29.4513 29.5362 29.3023 29.5958 29.1035L30.5893 26.0928C30.6985 25.7649 31.0562 25.586 31.3841 25.6953C31.5132 25.7351 31.6324 25.8245 31.7119 25.9437L33.5002 28.567C33.6194 28.7359 33.8082 28.8452 34.0168 28.8452L37.1959 28.8551C37.5437 28.8551 37.8218 29.1433 37.8218 29.491C37.8218 29.6302 37.7722 29.7593 37.6927 29.8786L35.7455 32.3925C35.6163 32.5614 35.5766 32.7701 35.6461 32.9688L36.6098 35.9895C36.7191 36.3174 36.5303 36.6751 36.2025 36.7844C36.0733 36.8242 35.9243 36.8241 35.7951 36.7745L32.8047 35.7013C32.6259 35.6119 32.4073 35.6417 32.2384 35.761Z" />
<svg width="160" height="160" viewBox="0 0 160 160" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="favicon-bg" fill="white" d="M140 0H20C8.9543 0 0 8.9543 0 20V140C0 151.046 8.9543 160 20 160H140C151.046 160 160 151.046 160 140V20C160 8.9543 151.046 0 140 0Z"/>
<path fill="#F6A044" fill-rule="evenodd" clip-rule="evenodd" d="M134 26C134 32.24 131.47 37.89 127.38 41.98C123.29 46.07 117.64 48.6 111.4 48.6H48.6V26H134Z"/>
<path id="favicon-corner" fill="#4C3A38" fill-rule="evenodd" clip-rule="evenodd" d="M48.6 26V48.6H26C26 36.12 36.12 26 48.6 26Z"/>
<path id="favicon-left" fill="#4C5ED5" fill-rule="evenodd" clip-rule="evenodd" d="M48.6 48.6V111.4C48.6 123.88 38.48 134 26 134V48.6H48.6Z"/>
<path id="favicon-star" fill="#4C5ED5" fill-rule="evenodd" clip-rule="evenodd" d="M80.34 93.04L71.52 97.68C70.55 98.19 69.36 97.82 68.85 96.85C68.65 96.46 68.58 96.02 68.65 95.6L70.33 85.78C70.44 85.14 70.23 84.48 69.76 84.03L62.63 77.08C61.85 76.32 61.83 75.07 62.59 74.28C62.89 73.97 63.29 73.77 63.72 73.7L73.58 72.27C74.22 72.18 74.78 71.77 75.07 71.19L79.48 62.26C79.96 61.28 81.15 60.88 82.13 61.36C82.52 61.55 82.84 61.87 83.03 62.26L87.44 71.19C87.73 71.77 88.29 72.18 88.93 72.27L98.79 73.7C99.87 73.86 100.62 74.86 100.46 75.94C100.4 76.37 100.19 76.77 99.88 77.07L92.75 84.02C92.28 84.47 92.07 85.13 92.18 85.77L93.86 95.59C94.04 96.67 93.32 97.69 92.24 97.87C91.81 97.94 91.37 97.87 90.99 97.67L82.17 93.03C81.6 92.74 80.91 92.74 80.34 93.04Z"/>
<style>
@media (prefers-color-scheme: dark) {
#favicon-bg {
fill: #495c70;
}
#favicon-fg {
fill: #1e262f;
}
#favicon-top {
fill: #f6a044;
}
#favicon-corner {
fill: #f9cdff;
fill: #FFCAFF;
}
#favicon-left {
fill: #728dff;
fill: #6E8BFF;
}
#favicon-star {
fill: #728dff;
fill: #6E8BFF;
}
}
</style>
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 541 KiB

After

Width:  |  Height:  |  Size: 363 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Some files were not shown because too many files have changed in this diff Show More