From 6f6de426da72ae518660b5e6423a1643721f19e5 Mon Sep 17 00:00:00 2001 From: Jake Jarvis Date: Wed, 1 Dec 2021 14:43:22 -0500 Subject: [PATCH] move dark mode code from module back to here for now --- assets/js/{index.js => main.js} | 0 assets/js/src/dark-mode.js | 108 +++++++++++++++++++++------ assets/sass/abstracts/_settings.scss | 4 - assets/sass/abstracts/_themes.scss | 13 +--- assets/sass/components/_global.scss | 6 +- assets/sass/components/_syntax.scss | 6 -- package.json | 2 - webpack.config.js | 2 +- yarn.lock | 35 ++++----- 9 files changed, 106 insertions(+), 70 deletions(-) rename assets/js/{index.js => main.js} (100%) diff --git a/assets/js/index.js b/assets/js/main.js similarity index 100% rename from assets/js/index.js rename to assets/js/main.js diff --git a/assets/js/src/dark-mode.js b/assets/js/src/dark-mode.js index c8b599c9..89455ac3 100644 --- a/assets/js/src/dark-mode.js +++ b/assets/js/src/dark-mode.js @@ -1,25 +1,91 @@ -import { init as initDarkMode } from "dark-mode-switcheroo"; +// use a specified element(s) to trigger swap when clicked +const toggle = document.querySelector(".dark-mode-toggle"); -// HACK: disable theme transition until user's preference is auto-restored (1/2) -const disableTransitionCSSHack = document.createElement("style"); -document.head.append(disableTransitionCSSHack); -disableTransitionCSSHack.sheet.insertRule(` - *, - ::before, - ::after { - transition-property: none !important; +// check for existing preference in local storage +const storageKey = "theme"; +const pref = localStorage.getItem(storageKey); + +// prepare a temporary stylesheet for fancy transitions +const fadeStyle = document.createElement("style"); + +// change CSS via these classes: +const dark = "dark"; +const light = "light"; + +// which class is set to initially? +const defaultTheme = light; + +// keep track of current state no matter how we got there +let active = defaultTheme === dark; + +// receives a class name and switches to it +const activateTheme = (theme, opts) => { + if (opts?.fade) { + document.head.append(fadeStyle); + + // apply a short transition to all properties of all elements + // TODO: this causes some extreme performance hiccups (especially in chromium) + fadeStyle.sheet.insertRule(` + *, ::before, ::after { + transition: all 0.15s linear !important; + } + `); + + // remove the stylesheet when body is done transitioning + document.body.addEventListener("transitionend", () => { + fadeStyle.remove(); + }); } -`); -initDarkMode({ - toggle: document.querySelector(".dark-mode-toggle"), - onInit: (t) => { - // make toggle visible now that we know JS is enabled - t.style.display = "block"; + document.body.classList.remove(dark, light); + document.body.classList.add(theme); + active = theme === dark; - // HACK: re-enable theme transitions after a very short delay, otherwise there's a weird race condition (2/2) - setTimeout(() => { - disableTransitionCSSHack.remove(); - }, 500); - }, -}); + if (opts?.save) { + localStorage.setItem(storageKey, theme); + } +}; + +// user has never clicked the button, so go by their OS preference until/if they do so +if (!pref) { + // returns media query selector syntax + // https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme + const prefers = (colorScheme) => `(prefers-color-scheme: ${colorScheme})`; + + // check for OS dark/light mode preference and switch accordingly + // default to `defaultTheme` set above if unsupported + if (window.matchMedia(prefers("dark")).matches) { + activateTheme(dark); + } else if (window.matchMedia(prefers("light")).matches) { + activateTheme(light); + } else { + activateTheme(defaultTheme); + } + + // real-time switching (if supported by OS/browser) + window.matchMedia(prefers("dark")).addEventListener("change", (e) => e.matches && activateTheme(dark)); + window.matchMedia(prefers("light")).addEventListener("change", (e) => e.matches && activateTheme(light)); +} else if (pref === dark || pref === light) { + // if user already explicitly toggled in the past, restore their preference + activateTheme(pref); +} else { + // fallback to default theme (this shouldn't happen) + activateTheme(defaultTheme); +} + +// don't freak out if page happens not to have a toggle +if (toggle) { + // make toggle visible now that we know JS is enabled + toggle.style.display = "block"; + + // handle toggle click + toggle.addEventListener("click", () => { + // switch to the opposite theme & save preference in local storage + // TODO: enable fade. + if (active) { + activateTheme(light, { save: true }); + } else { + activateTheme(dark, { save: true }); + } + }); +} diff --git a/assets/sass/abstracts/_settings.scss b/assets/sass/abstracts/_settings.scss index 88e4dba1..46597f97 100644 --- a/assets/sass/abstracts/_settings.scss +++ b/assets/sass/abstracts/_settings.scss @@ -34,7 +34,3 @@ $font-stack-mono-variable: list.join($webfont-mono-variable, $system-fonts-mono) // Fancy link underline settings: $link-underline-opacity: 40%; $link-underline-size: 2px; - -// Default fading style when switching between light/dark themes: -$theme-transition-duration: 0.15s; -$theme-transition-function: linear; diff --git a/assets/sass/abstracts/_themes.scss b/assets/sass/abstracts/_themes.scss index 8e658d76..3253bc38 100644 --- a/assets/sass/abstracts/_themes.scss +++ b/assets/sass/abstracts/_themes.scss @@ -5,18 +5,7 @@ // Takes a map of CSS properties and theme keys (see below) and set both body.light and body.dark selectors. // ex. @include themes.themed((color: "text", background-color: "background-inner")); -// Also accepts additional transitions (in shorthand) to tack on. -@mixin themed($properties, $moreTransitions: ()) { - // generate CSS transition shorthand for each themed property w/ default duration and function - $defaults: (); - @each $property, $color in $properties { - $shorthand: $property settings.$theme-transition-duration settings.$theme-transition-function; - $defaults: list.append($defaults, $shorthand); - } - - // list all transitions separated by commas (with additional shorthand(s) passed in) - transition: list.join($moreTransitions, $defaults, $separator: comma); - +@mixin themed($properties) { // keep track of the original selector(s) calling this mixin for below $selectors: #{&}; diff --git a/assets/sass/components/_global.scss b/assets/sass/components/_global.scss index 7abb472b..b6b83b78 100644 --- a/assets/sass/components/_global.scss +++ b/assets/sass/components/_global.scss @@ -99,13 +99,11 @@ main { background-repeat: no-repeat; background-size: 0% settings.$link-underline-size; padding-bottom: settings.$link-underline-size; + transition: background-size 0.25s ease-in-out; @include themes.themed( - $properties: ( + ( color: "links", - ), - $moreTransitions: ( - background-size 0.25s ease-in-out, ) ); diff --git a/assets/sass/components/_syntax.scss b/assets/sass/components/_syntax.scss index cb05c341..a10567a2 100644 --- a/assets/sass/components/_syntax.scss +++ b/assets/sass/components/_syntax.scss @@ -133,9 +133,6 @@ body.light { :not(pre) > code { background-color: #fbfbfb; border-color: #d5d5d5; - - // TODO: apply this consistently via themed() mixin like everywhere else - transition: color 0.15s linear, background-color 0.15s linear, border-color 0.15s linear; } div.highlight, @@ -224,9 +221,6 @@ body.dark { :not(pre) > code { background-color: #252525; border-color: #535353; - - // TODO: apply this consistently via themed() mixin like everywhere else - transition: color 0.15s linear, background-color 0.15s linear, border-color 0.15s linear; } div.highlight, diff --git a/package.json b/package.json index bf570376..10d388bf 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "url": "https://github.com/jakejarvis/jarv.is.git" }, "type": "module", - "main": "./assets/js/index.js", "scripts": { "build": "gulp", "clean": "gulp clean", @@ -32,7 +31,6 @@ "@primer/octicons-react": "^16.1.1", "@sentry/node": "^6.15.0", "clipboard-copy": "^4.0.1", - "dark-mode-switcheroo": "^0.10.0", "dayjs": "^1.10.7", "faunadb": "^4.4.1", "get-canonical-url": "^1.1.0", diff --git a/webpack.config.js b/webpack.config.js index 38470eff..345ade49 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,7 +21,7 @@ export default (env, argv) => { const globalBannerText = `Licensed under MIT. Copyright (c) 2015-${new Date().getFullYear()} Jake Jarvis .`; return { - entry: [path.resolve(__dirname, "assets/js/index.js"), path.resolve(__dirname, "assets/sass/main.scss")], + entry: [path.resolve(__dirname, "assets/js/main.js"), path.resolve(__dirname, "assets/sass/main.scss")], mode: isProd ? "production" : "development", devtool: isProd ? "source-map" : "inline-source-map", output: { diff --git a/yarn.lock b/yarn.lock index cd90323b..2aafc4d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -945,9 +945,9 @@ to-fast-properties "^2.0.0" "@discoveryjs/json-ext@^0.5.0": - version "0.5.5" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3" - integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA== + version "0.5.6" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f" + integrity sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA== "@eslint/eslintrc@^1.0.4": version "1.0.4" @@ -3003,11 +3003,6 @@ d@1, d@^1.0.1: es5-ext "^0.10.50" type "^1.0.1" -dark-mode-switcheroo@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/dark-mode-switcheroo/-/dark-mode-switcheroo-0.10.0.tgz#2b38085082fa7bf4f8047e9d082afe86a38281f6" - integrity sha512-/EIoyXUE/wmyBF8vRJMOaszCIqgjb2IrnFRREQvSMkYI6QHb3A9kVgs7w+laZAvj4If64bNXpAU6C09NJnGS7g== - data-uri-to-buffer@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" @@ -3320,9 +3315,9 @@ domelementtype@^2.0.1, domelementtype@^2.2.0: integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== domhandler@^4.2.0: - version "4.2.2" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f" - integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w== + version "4.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.0.tgz#16c658c626cf966967e306f966b431f77d4a5626" + integrity sha512-fC0aXNQXqKSFTr2wDNZDhsEYjCiYsDWl3D01kwt25hm1YIPyDGHvvi3rw+PLqHAl/m71MaiF7d5zvBr0p5UB2g== dependencies: domelementtype "^2.2.0" @@ -3441,9 +3436,9 @@ ee-first@1.1.1: integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= electron-to-chromium@^1.3.896: - version "1.4.5" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.5.tgz#912e8fd1645edee2f0f212558f40916eb538b1f9" - integrity sha512-YKaB+t8ul5crdh6OeqT2qXdxJGI0fAYb6/X8pDIyye+c3a7ndOCk5gVeKX+ABwivCGNS56vOAif3TN0qJMpEHw== + version "1.4.8" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.8.tgz#e1b7752ac1a75e39b5dd90cc7e29ea08b351c484" + integrity sha512-Cu5+dbg55+1E3ohlsa8HT0s4b8D0gBewXEGG8s5wBl8ynWv60VuvYW25GpsOeTVXpulhyU/U8JYZH+yxASSJBQ== emoji-regex@^8.0.0: version "8.0.0" @@ -9417,9 +9412,9 @@ svgo@^2.1.0, svgo@^2.5.0, svgo@^2.7.0: stable "^0.1.8" table@^6.7.3: - version "6.7.3" - resolved "https://registry.yarnpkg.com/table/-/table-6.7.3.tgz#255388439715a738391bd2ee4cbca89a4d05a9b7" - integrity sha512-5DkIxeA7XERBqMwJq0aHZOdMadBx4e6eDoFRuyT5VR82J0Ycg2DwM6GfA/EQAhJ+toRTaS1lIdSQCqgrmhPnlw== + version "6.7.5" + resolved "https://registry.yarnpkg.com/table/-/table-6.7.5.tgz#f04478c351ef3d8c7904f0e8be90a1b62417d238" + integrity sha512-LFNeryOqiQHqCVKzhkymKwt6ozeRhlm8IL1mE8rNUurkir4heF6PzMyRgaTa4tlyPTGGgXuvVOF/OLWiH09Lqw== dependencies: ajv "^8.0.1" lodash.truncate "^4.4.2" @@ -9769,9 +9764,9 @@ type-fest@^1.0.1: integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== type-fest@^2.0.0, type-fest@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.6.0.tgz#e9f1e78c5f746ca97ccbb873c59aa16c3bf6b123" - integrity sha512-XN1FDGGtaSDA6CFsCW5iolTQqFsnJ+ZF6JqSz0SqXoh4F8GY0xqUv5RYnTilpmL+sOH8OH4FX8tf9YyAPM2LDA== + version "2.8.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.8.0.tgz#39d7c9f9c508df8d6ce1cf5a966b0e6568dcc50d" + integrity sha512-O+V9pAshf9C6loGaH0idwsmugI2LxVNR7DtS40gVo2EXZVYFgz9OuNtOhgHLdHdapOEWNdvz9Ob/eeuaWwwlxA== type-is@~1.6.17, type-is@~1.6.18: version "1.6.18"