diff --git a/README.md b/README.md index 7875afb..ec99c40 100644 --- a/README.md +++ b/README.md @@ -149,10 +149,12 @@ SLUG-03.png ### 🌄 Favicon generation -This generates all required favcion sizes from: +This generates all required favicon sizes from: - `src/images/favicon-512.png` -- `src/images/favicon.svg` +- `src/images/favicon.svg` (handcrafted, adaptive based on OS theme) + +Also creates a web manifest. ```bash npm run favicon diff --git a/package-lock.json b/package-lock.json index fe01f0d..94edf3b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,13 +27,12 @@ "@svgr/webpack": "^6.5.1", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", - "@types/jest": "^29.2.2", + "@types/jest": "^29.2.3", "@types/js-yaml": "^4.0.5", "@types/sharp": "^0.31.0", "chalk": "^5.1.2", "eslint": "^8.27.0", "eslint-config-next": "^13.0.3", - "favicons": "^7.0.2", "jest": "^29.3.1", "jest-canvas-mock": "^2.4.0", "jest-environment-jsdom": "^29.3.1", @@ -44,11 +43,11 @@ "sharp": "^0.31.2", "sharp-ico": "^0.1.5", "slugify": "^1.6.5", - "stylelint": "^14.14.1", + "stylelint": "^14.15.0", "stylelint-config-prettier": "^9.0.4", "stylelint-prettier": "^2.0.0", "ts-node": "^10.9.1", - "typescript": "^4.8.4" + "typescript": "^4.9.3" }, "engines": { "node": "16.x" @@ -3598,9 +3597,9 @@ } }, "node_modules/@types/jest": { - "version": "29.2.2", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.2.tgz", - "integrity": "sha512-og1wAmdxKoS71K2ZwSVqWPX6OVn3ihZ6ZT2qvZvZQm90lJVDyXIjYcu4Khx2CNIeaFv12rOU/YObOsI3VOkzog==", + "version": "29.2.3", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.3.tgz", + "integrity": "sha512-6XwoEbmatfyoCjWRX7z0fKMmgYKe9+/HrviJ5k0X/tjJWHGAezZOfYaxqQKuzG/TvQyr+ktjm4jgbk0s4/oF2w==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -5472,12 +5471,6 @@ "node": ">=6" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -6204,20 +6197,6 @@ "reusify": "^1.0.4" } }, - "node_modules/favicons": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/favicons/-/favicons-7.0.2.tgz", - "integrity": "sha512-M/qE3ERHOBu0+Op+61jx8CdvOnSKrrl2zxUPpoGgsNyfjuGqRsK80zYoA5Uwdxl8QM4egfhBWZp1j7KK3YnOMg==", - "dev": true, - "dependencies": { - "escape-html": "^1.0.3", - "sharp": "^0.31.1", - "xml2js": "^0.4.23" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -11388,12 +11367,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -11924,15 +11897,15 @@ } }, "node_modules/stylelint": { - "version": "14.14.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.14.1.tgz", - "integrity": "sha512-Jnftu+lSD8cSpcV/+Z2nfgfgFpTIS1FcujezXPngtoIQ6wtwutL22MsNE0dJuMiM1h1790g2qIjAyUZCMrX4sw==", + "version": "14.15.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.15.0.tgz", + "integrity": "sha512-JOgDAo5QRsqiOZPZO+B9rKJvBm64S0xasbuRPAbPs6/vQDgDCnZLIiw6XcAS6GQKk9k1sBWR6rmH3Mfj8OknKg==", "dev": true, "dependencies": { "@csstools/selector-specificity": "^2.0.2", "balanced-match": "^2.0.0", "colord": "^2.9.3", - "cosmiconfig": "^7.0.1", + "cosmiconfig": "^7.1.0", "css-functions-list": "^3.1.0", "debug": "^4.3.4", "fast-glob": "^3.2.12", @@ -11952,7 +11925,7 @@ "micromatch": "^4.0.5", "normalize-path": "^3.0.0", "picocolors": "^1.0.0", - "postcss": "^8.4.18", + "postcss": "^8.4.19", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", "postcss-safe-parser": "^6.0.0", @@ -12482,9 +12455,9 @@ } }, "node_modules/typescript": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", - "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.3.tgz", + "integrity": "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -13078,28 +13051,6 @@ "node": ">=12" } }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dev": true, - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", diff --git a/package.json b/package.json index 5db726e..b15bca8 100644 --- a/package.json +++ b/package.json @@ -42,13 +42,12 @@ "@svgr/webpack": "^6.5.1", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", - "@types/jest": "^29.2.2", + "@types/jest": "^29.2.3", "@types/js-yaml": "^4.0.5", "@types/sharp": "^0.31.0", "chalk": "^5.1.2", "eslint": "^8.27.0", "eslint-config-next": "^13.0.3", - "favicons": "^7.0.2", "jest": "^29.3.1", "jest-canvas-mock": "^2.4.0", "jest-environment-jsdom": "^29.3.1", @@ -57,12 +56,13 @@ "prepend": "^1.0.2", "prettier": "^2.7.1", "sharp": "^0.31.2", + "sharp-ico": "^0.1.5", "slugify": "^1.6.5", - "stylelint": "^14.14.1", + "stylelint": "^14.15.0", "stylelint-config-prettier": "^9.0.4", "stylelint-prettier": "^2.0.0", "ts-node": "^10.9.1", - "typescript": "^4.8.4" + "typescript": "^4.9.3" }, "engines": { "node": "16.x" diff --git a/public/favicon.svg b/public/favicon.svg index 227fa1a..88b672d 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -5,12 +5,6 @@ viewBox="0 0 512 512" > - + diff --git a/public/manifest/manifest.webmanifest b/public/manifest/manifest.webmanifest index 4cf7846..3f7f2dd 100644 --- a/public/manifest/manifest.webmanifest +++ b/public/manifest/manifest.webmanifest @@ -1 +1 @@ -{"name":"matthias kretschmann","shortName":"mk","icons":[{"src":"/manifest/favicon-192.png","type":"image/png","sizes":"192x192"},{"src":"/manifest/favicon-512.png","type":"image/png","sizes":"512x512"}]} \ No newline at end of file +{"name":"matthias kretschmann","short_name":"mk","display":"standalone","start_url":"/?homescreen=1","icons":[{"src":"/manifest/favicon-192.png","type":"image/png","sizes":"192x192"},{"src":"/manifest/favicon-512.png","type":"image/png","sizes":"512x512"}]} \ No newline at end of file diff --git a/scripts/favicon.ts b/scripts/favicon.ts index 02d8ba5..e4d0254 100644 --- a/scripts/favicon.ts +++ b/scripts/favicon.ts @@ -25,7 +25,9 @@ const outputMeta = ` function createManifest(iconsizes: number[]) { const manifest = { name: 'matthias kretschmann', - shortName: 'mk', + short_name: 'mk', + display: 'standalone', + start_url: '/?homescreen=1', icons: iconsizes.map((size) => ({ src: `/manifest/favicon-${size}.png`, type: 'image/png', @@ -38,13 +40,18 @@ function createManifest(iconsizes: number[]) { ) } +function nuke() { + fs.rmSync(outputManifest, { recursive: true, force: true }) + fs.rmSync(`${outputWebRoot}/apple-touch-icon.png`, { force: true }) + fs.rmSync(`${outputWebRoot}/favicon.ico`, { force: true }) + fs.rmSync(`${outputWebRoot}/favicon.svg`, { force: true }) + fs.mkdirSync(outputManifest, { recursive: true }) +} + async function buildFavicons() { try { // Nuke all & create output folder first - fs.rmSync(outputManifest, { recursive: true, force: true }) - fs.rmSync(`${outputWebRoot}/apple-touch-icon.png`, { force: true }) - fs.rmSync(`${outputWebRoot}/favicon.ico`, { force: true }) - fs.mkdirSync(outputManifest, { recursive: true }) + nuke() // copy over the svg, as it's handcrafted fs.copyFileSync(faviconSourceSvg, `${outputWebRoot}/favicon.svg`) @@ -55,6 +62,7 @@ async function buildFavicons() { let destination = `${outputManifest}/favicon-${size}.png` if (size === 180) destination = `${outputWebRoot}/apple-touch-icon.png` + // 32px size only used for favicon.ico if (size === 32) { await ico.sharpsToIco( [sharp(faviconSource)], diff --git a/src/components/ThemeSwitch/ThemeToggle.tsx b/src/components/ThemeSwitch/ThemeToggle.tsx index 25845c8..2bc715f 100644 --- a/src/components/ThemeSwitch/ThemeToggle.tsx +++ b/src/components/ThemeSwitch/ThemeToggle.tsx @@ -1,8 +1,7 @@ -import React from 'react' import Icon from '../Icon' import styles from './index.module.css' -export const ThemeToggle = ({ dark }) => ( +export const ThemeToggle = () => ( diff --git a/src/components/ThemeSwitch/ThemeToggleInput.tsx b/src/components/ThemeSwitch/ThemeToggleInput.tsx index 317e446..484e37c 100644 --- a/src/components/ThemeSwitch/ThemeToggleInput.tsx +++ b/src/components/ThemeSwitch/ThemeToggleInput.tsx @@ -1,17 +1,16 @@ -import React from 'react' +import useDarkMode from '../../hooks/useDarkMode' -type Props = { - dark: boolean - toggleDark: () => void +export const ThemeToggleInput = () => { + const { isDarkMode, setIsDarkMode } = useDarkMode() + + return ( + setIsDarkMode(!isDarkMode)} + type="checkbox" + name="toggle" + value="toggle" + aria-describedby="toggle" + checked={isDarkMode === true} + /> + ) } - -export const ThemeToggleInput = ({ dark, toggleDark }: Props) => ( - toggleDark()} - type="checkbox" - name="toggle" - value="toggle" - aria-describedby="toggle" - checked={dark} - /> -) diff --git a/src/components/ThemeSwitch/index.tsx b/src/components/ThemeSwitch/index.tsx index ad23d15..fe3d0e1 100644 --- a/src/components/ThemeSwitch/index.tsx +++ b/src/components/ThemeSwitch/index.tsx @@ -5,7 +5,7 @@ import { ThemeToggle } from './ThemeToggle' import { ThemeToggleInput } from './ThemeToggleInput' export default function ThemeSwitch() { - const { value, toggle, themeColor } = useDarkMode() + const { themeColor } = useDarkMode() return ( <> @@ -20,8 +20,8 @@ export default function ThemeSwitch() { diff --git a/src/hooks/useDarkMode.ts b/src/hooks/useDarkMode.ts index 24741e1..711b0fe 100644 --- a/src/hooks/useDarkMode.ts +++ b/src/hooks/useDarkMode.ts @@ -2,7 +2,13 @@ // adapted from // https://github.com/daveschumaker/react-dark-mode-hook/blob/master/useDarkMode.js // -import { useState, useEffect, useCallback } from 'react' +import { + useState, + useEffect, + useCallback, + Dispatch, + SetStateAction +} from 'react' const isClient = typeof window === 'object' @@ -24,37 +30,37 @@ function getDarkMode() { } } -export default function useDarkMode() { - const [darkMode, setDarkMode] = useState() +export type UseDarkMode = { + isDarkMode: boolean + themeColor: string + setIsDarkMode: Dispatch> +} + +export default function useDarkMode(): UseDarkMode { + const [isDarkMode, setIsDarkMode] = useState(getDarkMode()) const [themeColor, setThemeColor] = useState() - const toggleDarkMode = useCallback(() => { - setDarkMode(!darkMode) - }, [darkMode]) + const changeTheme = useCallback(() => { + if (isDarkMode) { + document.documentElement.classList.add('dark') + } else { + document.documentElement.classList.remove('dark') + } + setThemeColor(isDarkMode === true ? '#1d2224' : '#e7eef4') + }, [isDarkMode]) // // Init // useEffect(() => { - const prefersDark = getDarkMode() - setDarkMode(prefersDark) - }, []) - - // - // Do things when darkMode changes - // - useEffect(() => { - const bodyClassList = document.querySelector('body').classList - bodyClassList.toggle('dark') - bodyClassList.toggle('light') - setThemeColor(darkMode === true ? '#1d2224' : '#e7eef4') - }, [darkMode]) + changeTheme() + }, [changeTheme]) // // Handle system theme change events // const handleChange = useCallback(() => { - setDarkMode(getDarkMode()) + setIsDarkMode(getDarkMode()) }, []) useEffect(() => { @@ -74,5 +80,5 @@ export default function useDarkMode() { .removeEventListener('change', handleChange) }, [handleChange]) - return { value: darkMode, toggle: toggleDarkMode, themeColor } + return { isDarkMode, setIsDarkMode, themeColor } } diff --git a/src/images/favicon.svg b/src/images/favicon.svg index 227fa1a..88b672d 100644 --- a/src/images/favicon.svg +++ b/src/images/favicon.svg @@ -5,12 +5,6 @@ viewBox="0 0 512 512" > - +