diff --git a/.all-contributorsrc b/.all-contributorsrc index 06f0dbb..6c01068 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -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 diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..8d31df2 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules +.next +storybook-static \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 4a62ec9..c752bfb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -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": [ { diff --git a/.github/workflows/commitlint.yml b/.github/workflows/commitlint.yml index 1f2b70f..af71e31 100644 --- a/.github/workflows/commitlint.yml +++ b/.github/workflows/commitlint.yml @@ -4,6 +4,7 @@ on: pull_request jobs: lint: + name: Lint commit messages runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b534d30..3cbae9e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eec7b56..8ca4281 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..b3f88bc --- /dev/null +++ b/.github/workflows/tests.yml @@ -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 diff --git a/.gitignore b/.gitignore index fd07293..a19c5fb 100644 --- a/.gitignore +++ b/.gitignore @@ -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 \ No newline at end of file +# 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 \ No newline at end of file diff --git a/.huskyrc.json b/.huskyrc.json index 6091775..932578e 100644 --- a/.huskyrc.json +++ b/.huskyrc.json @@ -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" + } } diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..9cd25a1 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +12.18.3 diff --git a/.storybook/main.js b/.storybook/main.js index 44cf475..4d89fb4 100644 --- a/.storybook/main.js +++ b/.storybook/main.js @@ -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 }, } diff --git a/.stylelintrc.json b/.stylelintrc.json index f52b8ef..5f77f7b 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -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"] + } + ] } } diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..66d552a --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "editorconfig.editorconfig", + "dbaeumer.vscode-eslint", + "syler.sass-indented", + "stylelint.vscode-stylelint", + "knisterpeter.vscode-commitizen" + ], + "unwantedRecommendations": ["esbenp.prettier-vscode"] +} diff --git a/README.md b/README.md index d04b8f6..f078289 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ A small online tool to help you generate your favicon in all the sizes and formats you need. -[![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-) ![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 - - - - + + + + +

Rui Saraiva

πŸš‡ πŸ’» πŸ“– πŸ’¬ 🚧

Eduardo Pinto

πŸ’» πŸ‘€

Augusto Lopes

🎨

RogΓ©rio Moreira

πŸ’» πŸ‘€

Rui Saraiva

πŸš‡ πŸ’» πŸ“– πŸ’¬ 🚧

Eduardo Pinto

πŸ’» πŸ‘€

Augusto Lopes

🎨

RogΓ©rio Moreira

πŸ’» πŸ‘€

Miguel Teixeira

πŸ’» πŸ‘€
- + diff --git a/components/base-layout/index.module.scss b/components/base-layout/index.module.scss index 69f7d4b..36a943e 100644 --- a/components/base-layout/index.module.scss +++ b/components/base-layout/index.module.scss @@ -2,4 +2,5 @@ .root { padding: 0; + overflow: hidden; } diff --git a/components/button/index.module.scss b/components/button/index.module.scss index e44dc07..1a83a6a 100644 --- a/components/button/index.module.scss +++ b/components/button/index.module.scss @@ -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; + } +} diff --git a/components/button/index.tsx b/components/button/index.tsx index c10e53a..b2ac0ce 100644 --- a/components/button/index.tsx +++ b/components/button/index.tsx @@ -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 = ({ diff --git a/components/code-highlight/index.tsx b/components/code-highlight/index.tsx index 63bd515..2def37b 100644 --- a/components/code-highlight/index.tsx +++ b/components/code-highlight/index.tsx @@ -7,13 +7,15 @@ type CodeHighlightProps = { template: string } -const CodeHighlight = ({ template }: CodeHighlightProps) => { +const CodeHighlight = React.forwardRef(({ template }, ref) => { const htmlCode = Prism.highlight(template, Prism.languages.markup, 'markup') return ( -
+		
 			
 		
) -} +}) + +CodeHighlight.displayName = 'CodeHighlight' export { CodeHighlight } diff --git a/components/dark-mode-toggle/index.module.scss b/components/dark-mode-toggle/index.module.scss index 549d29e..45db760 100644 --- a/components/dark-mode-toggle/index.module.scss +++ b/components/dark-mode-toggle/index.module.scss @@ -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; + } } } diff --git a/components/drag-and-drop/index.module.scss b/components/drag-and-drop/index.module.scss index 949baf1..fc458f3 100644 --- a/components/drag-and-drop/index.module.scss +++ b/components/drag-and-drop/index.module.scss @@ -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; + } +} diff --git a/components/drag-and-drop/index.tsx b/components/drag-and-drop/index.tsx index da06b10..646bc52 100644 --- a/components/drag-and-drop/index.tsx +++ b/components/drag-and-drop/index.tsx @@ -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 + onGenerate: (image: File, pwa: boolean, darkMode: boolean) => Promise 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() 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() const [isModalOpen, setIsModalOpen] = useState(false) + const modalBodyRef = useRef(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 ( -
-
+
+
{!image && !zipData && ( <>
{ onMouseEnter={() => setIsHover(true)} onMouseLeave={() => setIsHover(false)}> -
+
@@ -184,7 +215,23 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => { + className={classnames(styles.imageUploadText, styles.mobileUploadText, { + [styles.dragActive]: isDragActive, + })} + data-cy="drag-and-drop-text"> + + Generate all the sizes for your +
+ favicon. Tap to upload a file. +
+
+ Drag &  @@ -206,13 +253,17 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
- {/* */} - - We recommend a square PNG or SVG with at least 310px - - - No data or images are stored - +
+ + {' '} + + We recommend a square PNG or SVG with at least 310px + + + + No data or images are stored + +
)} {image && !zipData && ( @@ -223,7 +274,7 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => { Favicon preview
- + {title} { color="gray" tag="div" className={styles.filenameWrapper}> - + {imageData.name} {imageData.extension} - + ({imageSizes.width}x{imageSizes.height}) @@ -244,19 +295,21 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
- {isSquare ? : } + {isSquare ? : } Square image
- {isPngOrSvg ? : } + {isPngOrSvg ? : } PNG or SVG
- {isSvg || is310px ? : } + + {isSvg || is310px ? : } + 310px or higher @@ -268,7 +321,12 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
- setPwa(!pwa)}> + PWA compatible @@ -279,12 +337,17 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => {
- setDarkMode(!darkMode)}> + Dark Mode version - Available soon + Coming soon
@@ -358,37 +421,52 @@ const DragAndDrop = ({ onFile, onGenerate, onError }: DragAndDropProps) => { Download Favicon
- setIsModalOpen(false)} - className={styles.content} - overlayClassName={styles.overlay} - closeTimeoutMS={200} - contentLabel="Code generated"> -
-
- - Insert the following code in the <head> section of your pages: - - -
- -
-
-
-
- -
-
-
)} + setIsModalOpen(false)} + className={styles.content} + overlayClassName={styles.overlay} + closeTimeoutMS={200} + contentLabel="Code generated"> +
+
+ + Code + + +
+
+ + Insert the following code in the <head> section of your pages: + + +
+ +
+
+
+
+ + Insert the following code in the <head> section of your pages: + + + +
+ +
+
+
+
+
) diff --git a/components/favycon-error/index.module.scss b/components/favycon-error/index.module.scss index 1bb4e3c..41f4cb1 100644 --- a/components/favycon-error/index.module.scss +++ b/components/favycon-error/index.module.scss @@ -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; } diff --git a/components/favycon-error/index.tsx b/components/favycon-error/index.tsx index 8bc6d26..4094c5b 100644 --- a/components/favycon-error/index.tsx +++ b/components/favycon-error/index.tsx @@ -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 (
- + {error} diff --git a/components/favycon-info/index.module.scss b/components/favycon-info/index.module.scss index 2b1904d..1dc8e89 100644 --- a/components/favycon-info/index.module.scss +++ b/components/favycon-info/index.module.scss @@ -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; + } } diff --git a/components/favycon-info/index.tsx b/components/favycon-info/index.tsx index 81e082f..6fcf53c 100644 --- a/components/favycon-info/index.tsx +++ b/components/favycon-info/index.tsx @@ -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, HTMLDivElement>) => { const [isModalOpen, setIsModalOpen] = useState(false) + const modalBodyRef = useRef(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 ( -
- - - A small online tool to help you generate your favicon in all the sizes and formats you need. - - - Just drag & drop an image and you will then get a downloadable file alongside some documentation on how to - add the favicons. - -
- - Created by{' '} - {' '} - on their 2020’s worldwide quarantine. Background image from{' '} - - Unsplash - - . - +
+
+ +
+
+ + + A small online tool to help you generate your favicon in all the sizes and formats you need. + + + Just drag & drop an image and you will then get a downloadable file alongside some documentation on how to + add the favicons. + +
+ + Created by{' '} + {' '} + on their 2020’s worldwide quarantine. Background image from{' '} + + Unsplash + + . + +
{ overlayClassName={styles.overlay} closeTimeoutMS={200} contentLabel="About Us"> -
-
- {/* Favycon logo */} - - - Behind the curtains - - - For new updates be sure to follow these guys on Twitter. - +
+
+
+ + +
-
-
- {people.map((person) => ( -
-
- -
-
- - {person.name} - - - {person.role} - - - - {person.screenName} +
+ +
+ + + Behind the curtains + + + For new updates be sure to follow these guys on Twitter. + +
+
+
+ {people.map((person) => ( +
+
+ +
+
+ + {person.name} - + + {person.role} + + {person.social === 'twitter' ? ( + + + {person.screenName} + + + ) : ( + + + {person.screenName} + + + )} +
-
- ))} -
-
-
- - Project on{' '} - - GitHub - - . Have feedback? Send it to{' '} - - hello@favycon.app - - + ))} +
+
+
+ + Project on{' '} + + GitHub + + . Have feedback? Send it to{' '} + + hello@favycon.app + + +
diff --git a/components/favycon-wizard/index.module.scss b/components/favycon-wizard/index.module.scss index ab53668..200d582 100644 --- a/components/favycon-wizard/index.module.scss +++ b/components/favycon-wizard/index.module.scss @@ -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; } } diff --git a/components/favycon-wizard/index.tsx b/components/favycon-wizard/index.tsx index ffbc66d..ec6c580 100644 --- a/components/favycon-wizard/index.tsx +++ b/components/favycon-wizard/index.tsx @@ -10,17 +10,21 @@ type FavyconWizardProps = { showDndImage: boolean } +const unsplashImageProps = (isMobile: boolean) => ({ + src: `/images/unsplash${isMobile ? '-vertical' : ''}@1x.jpg`, + srcRetina: `/images/unsplash${isMobile ? '-vertical' : ''}@2x.jpg`, + srcPlaceholder: `/images/unsplash${isMobile ? '-vertical' : ''}@placeholder.jpg`, + alt: 'Unsplash', + aspectRatio: isMobile ? '614/768' : '540/354', + stretch: isMobile, +}) + const FavyconWizardComponent = ({ children, showDndImage }: FavyconWizardProps) => { return (
- + +
{children}
diff --git a/components/header/index.module.scss b/components/header/index.module.scss index cd83895..7a9e504 100644 --- a/components/header/index.module.scss +++ b/components/header/index.module.scss @@ -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; + } } diff --git a/components/header/index.tsx b/components/header/index.tsx index 755056b..9dac569 100644 --- a/components/header/index.tsx +++ b/components/header/index.tsx @@ -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 = () => {
+
+ +
diff --git a/components/lazy-image/index.module.scss b/components/lazy-image/index.module.scss index 9388e59..a90bb60 100644 --- a/components/lazy-image/index.module.scss +++ b/components/lazy-image/index.module.scss @@ -3,6 +3,11 @@ .root { position: relative; width: 100%; + + &.stretch { + height: 100%; + padding-bottom: 0 !important; + } } .image { diff --git a/components/lazy-image/index.tsx b/components/lazy-image/index.tsx index e546510..3504dab 100644 --- a/components/lazy-image/index.tsx +++ b/components/lazy-image/index.tsx @@ -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, 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 (
({ isMobile: false }) + +export const MediaQueryProvider = ({ children, isMobileDevice = false }: MediaQueryProviderProps) => { + const isMobile = useMedia('(max-width: 991px)', isMobileDevice) + const value = useMemo(() => ({ isMobile }), [isMobile]) + + return {children} +} + +MediaQueryProvider.propTypes = { + children: PropTypes.node.isRequired, + isMobileDevice: PropTypes.bool, +} + +export const useMediaQueryContext = () => useContext(MediaQueryContext) diff --git a/components/seo/index.tsx b/components/seo/index.tsx index 841158e..88517a6 100644 --- a/components/seo/index.tsx +++ b/components/seo/index.tsx @@ -19,6 +19,7 @@ const SEO = ({ title, description }: SEOProps) => ( + @@ -32,16 +33,11 @@ const SEO = ({ title, description }: SEOProps) => ( - + - - - - - - + ) diff --git a/components/svgs/svg-appycon.tsx b/components/svgs/svg-appycon.tsx deleted file mode 100644 index fcfa1aa..0000000 --- a/components/svgs/svg-appycon.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react' -import { uniqueId } from 'utils/ids' - -const SvgAppycon: React.FC> = (props) => { - const idPrefix = uniqueId('svg-') - return ( - - Appycon logo - - - - - - - - - - - - - - - - - - - - - - ) -} - -export { SvgAppycon } diff --git a/components/svgs/svg-arrow-down.tsx b/components/svgs/svg-arrow-down.tsx deleted file mode 100644 index d308edd..0000000 --- a/components/svgs/svg-arrow-down.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import useDarkMode from 'use-dark-mode' -import { uniqueId } from 'utils/ids' - -const SvgArrowDown: React.FC> = (props) => { - const { value: isDark } = useDarkMode(false) - const idPrefix = uniqueId('svg-') - return ( - - Arrow down - - - ) -} - -export { SvgArrowDown } diff --git a/components/svgs/svg-checkbox.module.scss b/components/svgs/svg-checkbox.module.scss index 025bbe0..72fa6b1 100644 --- a/components/svgs/svg-checkbox.module.scss +++ b/components/svgs/svg-checkbox.module.scss @@ -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; } } diff --git a/components/svgs/svg-drop-zone.module.scss b/components/svgs/svg-drop-zone.module.scss index 25f7102..6419ac1 100644 --- a/components/svgs/svg-drop-zone.module.scss +++ b/components/svgs/svg-drop-zone.module.scss @@ -3,6 +3,10 @@ .rect { stroke: #ccc; transition: stroke 0.2s ease; + + @include breakpoint-md { + stroke-width: 0; + } } :global(.dark-mode) .rect { diff --git a/components/svgs/svg-favycon.tsx b/components/svgs/svg-favycon.tsx index aba6990..8b350a7 100644 --- a/components/svgs/svg-favycon.tsx +++ b/components/svgs/svg-favycon.tsx @@ -1,10 +1,9 @@ import React from 'react' -import { uniqueId } from 'utils/ids' import styles from './svg-favycon.module.scss' const SvgFavycon: React.FC> = (props) => { - const idPrefix = uniqueId('svg-') + const idPrefix = 'svg-favycon-' return ( Favycon logo diff --git a/components/svgs/svg-github.tsx b/components/svgs/svg-github.tsx new file mode 100644 index 0000000..0738ca4 --- /dev/null +++ b/components/svgs/svg-github.tsx @@ -0,0 +1,12 @@ +import React from 'react' + +const SvgGithub: React.FC> = (props) => ( + + + +) + +export { SvgGithub } diff --git a/components/svgs/svg-image-upload.module.scss b/components/svgs/svg-image-upload.module.scss index f72ce77..4532ef1 100644 --- a/components/svgs/svg-image-upload.module.scss +++ b/components/svgs/svg-image-upload.module.scss @@ -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; + } } diff --git a/components/svgs/svg-image-upload.tsx b/components/svgs/svg-image-upload.tsx index 7d534d6..494edcc 100644 --- a/components/svgs/svg-image-upload.tsx +++ b/components/svgs/svg-image-upload.tsx @@ -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 { - const idPrefix = uniqueId('svg-') + const idPrefix = 'svg-image-upload-' return ( Image Upload - - + + + ) } diff --git a/components/svgs/svg-info.tsx b/components/svgs/svg-info.tsx index fcfb4d9..0ec4256 100644 --- a/components/svgs/svg-info.tsx +++ b/components/svgs/svg-info.tsx @@ -1,10 +1,9 @@ import React from 'react' -import { uniqueId } from 'utils/ids' import styles from './svg-info.module.scss' const SvgInfo: React.FC> = (props) => { - const idPrefix = uniqueId('svg-') + const idPrefix = 'svg-info-' return ( Info diff --git a/components/svgs/svg-moon.tsx b/components/svgs/svg-moon.tsx index 800a09a..3a4675c 100644 --- a/components/svgs/svg-moon.tsx +++ b/components/svgs/svg-moon.tsx @@ -1,8 +1,7 @@ import React from 'react' -import { uniqueId } from 'utils/ids' const SvgMoon: React.FC> = (props) => { - const idPrefix = uniqueId('svg-') + const idPrefix = 'svg-moon-' return ( Activate dark mode diff --git a/components/svgs/svg-sun.tsx b/components/svgs/svg-sun.tsx index dd5731a..9ffcbc4 100644 --- a/components/svgs/svg-sun.tsx +++ b/components/svgs/svg-sun.tsx @@ -1,8 +1,7 @@ import React from 'react' -import { uniqueId } from 'utils/ids' const SvgSun: React.FC> = (props) => { - const idPrefix = uniqueId('svg-') + const idPrefix = 'svg-sun-' return ( Activate light mode diff --git a/components/tool-title/index.tsx b/components/tool-title/index.tsx index d88e123..e8d4549 100644 --- a/components/tool-title/index.tsx +++ b/components/tool-title/index.tsx @@ -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) => (
- - + {!hideLogo && } + Favycon diff --git a/components/typography/index.module.scss b/components/typography/index.module.scss index 1374a6c..127d07e 100644 --- a/components/typography/index.module.scss +++ b/components/typography/index.module.scss @@ -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; } diff --git a/components/typography/index.tsx b/components/typography/index.tsx index e35f338..06a2f33 100644 --- a/components/typography/index.tsx +++ b/components/typography/index.tsx @@ -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, 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 } diff --git a/cypress.json b/cypress.json new file mode 100644 index 0000000..4a68c37 --- /dev/null +++ b/cypress.json @@ -0,0 +1,5 @@ +{ + "baseUrl": "http://localhost:3000", + "viewportWidth": 1280, + "viewportHeight": 720 +} diff --git a/cypress/.eslintrc.json b/cypress/.eslintrc.json new file mode 100644 index 0000000..3682e27 --- /dev/null +++ b/cypress/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": ["plugin:cypress/recommended"], + "rules": { + "cypress/no-unnecessary-waiting": "off" + } +} diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 0000000..0ff2d64 --- /dev/null +++ b/cypress/fixtures/example.json @@ -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" +} diff --git a/cypress/fixtures/images/favicon-150x150.png b/cypress/fixtures/images/favicon-150x150.png new file mode 100644 index 0000000..8bb470c Binary files /dev/null and b/cypress/fixtures/images/favicon-150x150.png differ diff --git a/cypress/fixtures/images/favicon-160x160.jpg b/cypress/fixtures/images/favicon-160x160.jpg new file mode 100644 index 0000000..41329eb Binary files /dev/null and b/cypress/fixtures/images/favicon-160x160.jpg differ diff --git a/cypress/fixtures/images/favicon-310x310.png b/cypress/fixtures/images/favicon-310x310.png new file mode 100644 index 0000000..f367937 Binary files /dev/null and b/cypress/fixtures/images/favicon-310x310.png differ diff --git a/cypress/fixtures/images/favicon-320x320.jpg b/cypress/fixtures/images/favicon-320x320.jpg new file mode 100644 index 0000000..1264ec5 Binary files /dev/null and b/cypress/fixtures/images/favicon-320x320.jpg differ diff --git a/cypress/fixtures/images/favicon-360x320.jpg b/cypress/fixtures/images/favicon-360x320.jpg new file mode 100644 index 0000000..b5ee9cc Binary files /dev/null and b/cypress/fixtures/images/favicon-360x320.jpg differ diff --git a/cypress/fixtures/images/favicon-360x320.png b/cypress/fixtures/images/favicon-360x320.png new file mode 100644 index 0000000..122d0c1 Binary files /dev/null and b/cypress/fixtures/images/favicon-360x320.png differ diff --git a/cypress/fixtures/images/favicon-640x640.png b/cypress/fixtures/images/favicon-640x640.png new file mode 100644 index 0000000..668263c Binary files /dev/null and b/cypress/fixtures/images/favicon-640x640.png differ diff --git a/cypress/fixtures/images/favicon.svg b/cypress/fixtures/images/favicon.svg new file mode 100644 index 0000000..a7fde6d --- /dev/null +++ b/cypress/fixtures/images/favicon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/cypress/integration/drag-and-drop.spec.js b/cypress/integration/drag-and-drop.spec.js new file mode 100644 index 0000000..5a0174a --- /dev/null +++ b/cypress/integration/drag-and-drop.spec.js @@ -0,0 +1,98 @@ +/// + +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') + }) + }) +}) diff --git a/cypress/integration/sample.spec.js b/cypress/integration/sample.spec.js new file mode 100644 index 0000000..d56aeb2 --- /dev/null +++ b/cypress/integration/sample.spec.js @@ -0,0 +1,7 @@ +/// + +describe('My First Test', () => { + it('Visits the Favycon', () => { + cy.visit('/') + }) +}) diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js new file mode 100644 index 0000000..a37690d --- /dev/null +++ b/cypress/plugins/index.js @@ -0,0 +1,21 @@ +/// +// *********************************************************** +// 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 +} diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 0000000..4ec2b1e --- /dev/null +++ b/cypress/support/commands.js @@ -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' diff --git a/cypress/support/index.js b/cypress/support/index.js new file mode 100644 index 0000000..d68db96 --- /dev/null +++ b/cypress/support/index.js @@ -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') diff --git a/hooks/use-toggle.ts b/hooks/use-toggle.ts new file mode 100644 index 0000000..ec101b7 --- /dev/null +++ b/hooks/use-toggle.ts @@ -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] +} diff --git a/lint-staged.config.js b/lint-staged.config.js index a650434..4674239 100644 --- a/lint-staged.config.js +++ b/lint-staged.config.js @@ -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'], } diff --git a/next.config.js b/next.config.js index cf54e5b..1fa69fe 100644 --- a/next.config.js +++ b/next.config.js @@ -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, +}) diff --git a/package.json b/package.json index 1f68b39..f50ed63 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/pages/_app.tsx b/pages/_app.tsx index b255b9e..15195f9 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -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 + return ( + + + + ) } export default MyApp diff --git a/pages/_document.tsx b/pages/_document.tsx index 9b9cfa8..34db86b 100644 --- a/pages/_document.tsx +++ b/pages/_document.tsx @@ -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 ( - + + {isProd &&