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"
>
-
+