Animated dark mode switcher

This is so fancy omg
This commit is contained in:
Martin Prokoph
2024-12-31 12:46:35 +01:00
parent 1146b04e3d
commit 55a38c8ac5
7 changed files with 1189 additions and 10 deletions

View File

@@ -1 +1 @@
[["Map",1,2],"meta::meta",["Map",3,4],"astro-version","5.0.2"] [["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.1.0","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"site\":\"https://www.midnightdust.eu\",\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[]},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":\"shiki\",\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"responsiveImages\":false},\"legacy\":{\"collections\":false}}"]

1091
.astro/icon.d.ts vendored

File diff suppressed because it is too large Load Diff

11
package-lock.json generated
View File

@@ -18,6 +18,7 @@
"@astrojs/partytown": "^2.1.2", "@astrojs/partytown": "^2.1.2",
"@astrojs/tailwind": "^5.1.3", "@astrojs/tailwind": "^5.1.3",
"@iconify-json/ion": "^1.2.0", "@iconify-json/ion": "^1.2.0",
"@iconify-json/line-md": "^1.2.4",
"@iconify-json/mdi": "^1.2.0", "@iconify-json/mdi": "^1.2.0",
"@iconify-json/simple-icons": "^1.2.3", "@iconify-json/simple-icons": "^1.2.3",
"@typescript-eslint/eslint-plugin": "^8.6.0", "@typescript-eslint/eslint-plugin": "^8.6.0",
@@ -926,6 +927,16 @@
"@iconify/types": "*" "@iconify/types": "*"
} }
}, },
"node_modules/@iconify-json/line-md": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@iconify-json/line-md/-/line-md-1.2.4.tgz",
"integrity": "sha512-zapvr+PM17evfekZwarZaNp4szd/2ELuFhNKqLw3hGqY4by8DWIE4c97yue9ooUJN54CQXPfVGFnxNEXeeZctQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@iconify/types": "*"
}
},
"node_modules/@iconify-json/mdi": { "node_modules/@iconify-json/mdi": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@iconify-json/mdi/-/mdi-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@iconify-json/mdi/-/mdi-1.2.0.tgz",

View File

@@ -15,6 +15,7 @@
"@astrojs/partytown": "^2.1.2", "@astrojs/partytown": "^2.1.2",
"@astrojs/tailwind": "^5.1.3", "@astrojs/tailwind": "^5.1.3",
"@iconify-json/ion": "^1.2.0", "@iconify-json/ion": "^1.2.0",
"@iconify-json/line-md": "^1.2.4",
"@iconify-json/mdi": "^1.2.0", "@iconify-json/mdi": "^1.2.0",
"@iconify-json/simple-icons": "^1.2.3", "@iconify-json/simple-icons": "^1.2.3",
"@typescript-eslint/eslint-plugin": "^8.6.0", "@typescript-eslint/eslint-plugin": "^8.6.0",

View File

@@ -18,7 +18,7 @@ import { Icon } from 'astro-icon/components'
<div class="flex" id="tabs-header"> <div class="flex" id="tabs-header">
{ {
tabs.map((tab) => ( tabs.map((tab) => (
<button class={`tab ${className} ${tab.disabled ? "disabled" : ""}`} id={tab.id+"-tab"} data-active={tab.active}> <button class={`tab ${className} ${tab.disabled ? "disabled" : ""}`} id={tab.id+"-tab"} data-active={tab.active ? "true" : "false"}>
<div class="flex"> <div class="flex">
{tab.icon ? <Icon name={tab.icon} class="mr-2" /> : ''} {tab.icon ? <Icon name={tab.icon} class="mr-2" /> : ''}
{tab.name} {tab.name}
@@ -34,11 +34,10 @@ import { Icon } from 'astro-icon/components'
</custom-tabs> </custom-tabs>
<script> <script>
// Define the behaviour for our new type of HTML element. // Define the behaviour for our new type of HTML element.
class CustomTabs extends HTMLElement { class CustomTabs extends HTMLElement {
connectedCallback() { connectedCallback() {
const buttons = document.querySelectorAll('button.tab'); const buttons = this.querySelectorAll('button.tab');
buttons.forEach((button) => { buttons.forEach((button) => {
button.addEventListener('click', () => { button.addEventListener('click', () => {

View File

@@ -0,0 +1,80 @@
---
import { Icon } from 'astro-icon/components'
---
<fancy-dark-mode>
<button class="darkmode-toggle mr-4 xl:mr-0" aria-expanded="false" aria-label="Toggle dark mode">
<div id="animation-icon" class="hidden"/>
<Icon is:inline={true} id="light" class="hidden" name="line-md:sunny" width="32" height="32"/>
<Icon is:inline={true} id="dark" class="hidden" name="line-md:moon" width="32" height="32" />
</button>
</fancy-dark-mode>
<style lang="scss">
.darkmode-toggle {
color: var(--action-color);
&:hover {
color: var(--action-color-state);
}
}
</style>
<script is:inline>
class FancyDarkMode extends HTMLElement {
connectedCallback() {
// variables
let darkMode = localStorage.getItem('darkMode')
let darkModeToggle = document.querySelector('.darkmode-toggle')
// functions
const toggleDarkMode = (store = true, initial = false, toDark) => {
const animationIcon = document.querySelector('#animation-icon')
const lightIcon = document.querySelector('#light')
const darkIcon = document.querySelector('#dark')
toDark ? document.body.classList.add('darkmode') : document.body.classList.remove('darkmode')
if (!initial) {
lightIcon.classList.add('hidden')
darkIcon.classList.add('hidden')
}
if (!toDark) {
// Icons from https://github.com/cyberalien/line-md
if (!initial) animationIcon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-dasharray="2" stroke-dashoffset="2" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M12 19v1M19 12h1M12 5v-1M5 12h-1"><animate fill="freeze" attributeName="d" begin="1.2s" dur="0.2s" values="M12 19v1M19 12h1M12 5v-1M5 12h-1;M12 21v1M21 12h1M12 3v-1M3 12h-1"/><animate fill="freeze" attributeName="stroke-dashoffset" begin="1.2s" dur="0.2s" values="2;0"/></path><path d="M17 17l0.5 0.5M17 7l0.5 -0.5M7 7l-0.5 -0.5M7 17l-0.5 0.5"><animate fill="freeze" attributeName="d" begin="1.4s" dur="0.2s" values="M17 17l0.5 0.5M17 7l0.5 -0.5M7 7l-0.5 -0.5M7 17l-0.5 0.5;M18.5 18.5l0.5 0.5M18.5 5.5l0.5 -0.5M5.5 5.5l-0.5 -0.5M5.5 18.5l-0.5 0.5"/><animate fill="freeze" attributeName="stroke-dashoffset" begin="1.4s" dur="0.2s" values="2;0"/></path></g><g fill="currentColor"><path d="M15.22 6.03L17.75 4.09L14.56 4L13.5 1L12.44 4L9.25 4.09L11.78 6.03L10.87 9.09L13.5 7.28L16.13 9.09L15.22 6.03Z"><animate fill="freeze" attributeName="fill-opacity" dur="0.4s" values="1;0"/></path><path d="M19.61 12.25L21.25 11L19.19 10.95L18.5 9L17.81 10.95L15.75 11L17.39 12.25L16.8 14.23L18.5 13.06L20.2 14.23L19.61 12.25Z"><animate fill="freeze" attributeName="fill-opacity" begin="0.2s" dur="0.4s" values="1;0"/></path></g><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 6 C7 12.08 11.92 17 18 17 C18.53 17 19.05 16.96 19.56 16.89 C17.95 19.36 15.17 21 12 21 C7.03 21 3 16.97 3 12 C3 8.83 4.64 6.05 7.11 4.44 C7.04 4.95 7 5.47 7 6 Z"><set fill="freeze" attributeName="opacity" begin="0.6s" to="0"/></path><mask id="lineMdMoonToSunnyOutlineTransition0"><circle cx="12" cy="12" r="12" fill="#fff"/><circle cx="12" cy="12" r="8"><animate fill="freeze" attributeName="r" begin="0.6s" dur="0.4s" values="8;4"/></circle><circle cx="18" cy="6" r="12" fill="#fff"><animate fill="freeze" attributeName="cx" begin="0.6s" dur="0.4s" values="18;22"/><animate fill="freeze" attributeName="cy" begin="0.6s" dur="0.4s" values="6;2"/><animate fill="freeze" attributeName="r" begin="0.6s" dur="0.4s" values="12;3"/></circle><circle cx="18" cy="6" r="10"><animate fill="freeze" attributeName="cx" begin="0.6s" dur="0.4s" values="18;22"/><animate fill="freeze" attributeName="cy" begin="0.6s" dur="0.4s" values="6;2"/><animate fill="freeze" attributeName="r" begin="0.6s" dur="0.4s" values="10;1"/></circle></mask><circle cx="12" cy="12" r="10" mask="url(#lineMdMoonToSunnyOutlineTransition0)" opacity="0" fill="currentColor"><animate fill="freeze" attributeName="r" begin="0.6s" dur="0.4s" values="10;6"/><set fill="freeze" attributeName="opacity" begin="0.6s" to="1"/></circle></svg>`
initial ? lightIcon.classList.remove('hidden') : animationIcon.classList.remove('hidden')
}
else {
// Icons from https://github.com/cyberalien/line-md
if (!initial) animationIcon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill-opacity="0" d="M15.22 6.03L17.75 4.09L14.56 4L13.5 1L12.44 4L9.25 4.09L11.78 6.03L10.87 9.09L13.5 7.28L16.13 9.09L15.22 6.03Z" fill="currentColor"><animate fill="freeze" attributeName="fill-opacity" begin="0.6s" dur="0.4s" values="0;1"/></path><path fill-opacity="0" d="M19.61 12.25L21.25 11L19.19 10.95L18.5 9L17.81 10.95L15.75 11L17.39 12.25L16.8 14.23L18.5 13.06L20.2 14.23L19.61 12.25Z" fill="currentColor"><animate fill="freeze" attributeName="fill-opacity" begin="1s" dur="0.4s" values="0;1"/></path><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><g><path stroke-dasharray="2" stroke-dashoffset="4" d="M12 21v1M21 12h1M12 3v-1M3 12h-1"><animate fill="freeze" attributeName="stroke-dashoffset" dur="0.2s" values="4;2"/></path><path stroke-dasharray="2" stroke-dashoffset="4" d="M18.5 18.5l0.5 0.5M18.5 5.5l0.5 -0.5M5.5 5.5l-0.5 -0.5M5.5 18.5l-0.5 0.5"><animate fill="freeze" attributeName="stroke-dashoffset" begin="0.2s" dur="0.2s" values="4;2"/></path><set fill="freeze" attributeName="opacity" begin="0.5s" to="0"/></g><path d="M7 6 C7 12.08 11.92 17 18 17 C18.53 17 19.05 16.96 19.56 16.89 C17.95 19.36 15.17 21 12 21 C7.03 21 3 16.97 3 12 C3 8.83 4.64 6.05 7.11 4.44 C7.04 4.95 7 5.47 7 6 Z" opacity="0"><set fill="freeze" attributeName="opacity" begin="0.5s" to="1"/></path></g><mask id="lineMdSunnyOutlineToMoonTransition0"><circle cx="12" cy="12" r="12" fill="#fff"/><circle cx="12" cy="12" r="4"><animate fill="freeze" attributeName="r" begin="0.1s" dur="0.4s" values="4;8"/></circle><circle cx="22" cy="2" r="3" fill="#fff"><animate fill="freeze" attributeName="cx" begin="0.1s" dur="0.4s" values="22;18"/><animate fill="freeze" attributeName="cy" begin="0.1s" dur="0.4s" values="2;6"/><animate fill="freeze" attributeName="r" begin="0.1s" dur="0.4s" values="3;12"/></circle><circle cx="22" cy="2" r="1"><animate fill="freeze" attributeName="cx" begin="0.1s" dur="0.4s" values="22;18"/><animate fill="freeze" attributeName="cy" begin="0.1s" dur="0.4s" values="2;6"/><animate fill="freeze" attributeName="r" begin="0.1s" dur="0.4s" values="1;10"/></circle></mask><circle cx="12" cy="12" r="6" mask="url(#lineMdSunnyOutlineToMoonTransition0)" fill="currentColor"><animate fill="freeze" attributeName="r" begin="0.1s" dur="0.4s" values="6;10"/><set fill="freeze" attributeName="opacity" begin="0.5s" to="0"/></circle></svg>`
initial ? darkIcon.classList.remove('hidden') : animationIcon.classList.remove('hidden')
}
toDark ? darkModeToggle.setAttribute('aria-pressed', 'true') : darkModeToggle.setAttribute('aria-pressed', 'false')
if (store) toDark ? localStorage.setItem('darkMode', 'enabled') : localStorage.setItem('darkMode', 'disabled')
//restartAnimation()
}
const checkPreference = () => {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
// We don't set localStorage from preferences to respect future changes in client prefs
toggleDarkMode(false, true, true)
} else {
toggleDarkMode(false, true, false)
}
}
// execution
if (darkMode === 'enabled') toggleDarkMode(true, true, true)
else if (darkMode === 'disabled') toggleDarkMode(true, true, false)
else if (!darkMode) checkPreference()
darkModeToggle.addEventListener('click', () => {
darkMode = document.body.classList.contains('darkmode')
toggleDarkMode(true, false, !darkMode)
})
}
}
customElements.define('fancy-dark-mode', FancyDarkMode);
</script>

View File

@@ -1,10 +1,7 @@
--- ---
import ResponsiveToggle from './ResponsiveToggle.astro' import ResponsiveToggle from './ResponsiveToggle.astro'
import { DarkMode } from 'accessible-astro-components'
import { Image } from 'astro:assets'
import logo from '../assets/img/logo.png'
import logo_lightmode from '../assets/img/logo_lightmode.png'
import { Icon } from 'astro-icon/components' import { Icon } from 'astro-icon/components'
import FancyDarkMode from './FancyDarkMode.astro'
--- ---
<script> <script>
@@ -23,7 +20,9 @@ import { Icon } from 'astro-icon/components'
<slot /> <slot />
</ul> </ul>
</nav> </nav>
<DarkMode /> <div class="pt-1.5">
<FancyDarkMode />
</div>
<ResponsiveToggle /> <ResponsiveToggle />
</div> </div>
<nav class="mobile-menu" aria-label="Main navigation mobile"> <nav class="mobile-menu" aria-label="Main navigation mobile">