mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-04-26 14:08:29 -04:00
move light/dark class from <body>
to <html>
and inline restoration script to (hopefully) improve the white flashes
This commit is contained in:
parent
c2789e24d4
commit
766362a9da
21
assets/js/restore-theme.js
Normal file
21
assets/js/restore-theme.js
Normal file
@ -0,0 +1,21 @@
|
||||
/* eslint-disable no-var */
|
||||
|
||||
// A super tiny script to restore dark mode off the bat (to hopefully avoid blinding flashes of white).
|
||||
// NOTE: This is inlined by Hugo/esbuild (see layouts/partials/head/restore-theme.html) instead of Webpack.
|
||||
|
||||
import { getDarkPref } from "./src/utils/theme.js";
|
||||
|
||||
try {
|
||||
var cl = document.documentElement.classList;
|
||||
var pref = getDarkPref();
|
||||
|
||||
// set `<html class="dark">` if either the user has explicitly toggled in the past or if their OS is in dark mode
|
||||
if (pref === "true" || (!pref && window.matchMedia("(prefers-color-scheme: dark)").matches)) {
|
||||
cl.remove("light");
|
||||
cl.add("dark");
|
||||
}
|
||||
|
||||
// TODO: fix real-time switching (works but bulb icon isn't updated)
|
||||
// window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => e.matches && setDark(true));
|
||||
// window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", (e) => e.matches && setDark(false));
|
||||
} catch (error) {}
|
@ -1,6 +1,6 @@
|
||||
import { h } from "preact";
|
||||
import { useState, useEffect } from "preact/hooks";
|
||||
import { isDark, setDarkClass, setDarkPref } from "../utils/theme.js";
|
||||
import { isDark, setDarkPref } from "../utils/theme.js";
|
||||
|
||||
// react components:
|
||||
import BulbOn from "../assets/bulb-on.svg";
|
||||
@ -11,7 +11,9 @@ const ThemeToggle = () => {
|
||||
const [dark, setDark] = useState(isDark());
|
||||
|
||||
useEffect(() => {
|
||||
return setDarkClass(dark);
|
||||
// sets appropriate `<html class="...">`
|
||||
document.documentElement.classList.remove("dark", "light");
|
||||
document.documentElement.classList.add(dark ? "dark" : "light");
|
||||
}, [dark]);
|
||||
|
||||
const handleToggle = () => {
|
||||
|
@ -1,28 +1,8 @@
|
||||
import { h, render } from "preact";
|
||||
import { getDarkPref, setDarkClass } from "./utils/theme.js";
|
||||
|
||||
// react components:
|
||||
import ThemeToggle from "./components/ThemeToggle.js";
|
||||
|
||||
// check for existing preference in local storage
|
||||
const pref = getDarkPref();
|
||||
|
||||
// do initialization before *any* react-related stuff to avoid white flashes as much as possible
|
||||
if (pref) {
|
||||
// restore user's preference if they've explicitly toggled it in the past
|
||||
setDarkClass(pref === "true");
|
||||
} else {
|
||||
// check for OS dark mode preference and switch accordingly
|
||||
// https://drafts.csswg.org/mediaqueries-5/#prefers-color-scheme
|
||||
try {
|
||||
setDarkClass(window.matchMedia("(prefers-color-scheme: dark)").matches);
|
||||
} catch (e) {}
|
||||
|
||||
// TODO: fix real-time switching (works but bulb icon isn't updated)
|
||||
// window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", (e) => e.matches && setDark(true));
|
||||
// window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", (e) => e.matches && setDark(false));
|
||||
}
|
||||
|
||||
// finally render the nifty lightbulb in the header
|
||||
if (typeof window !== "undefined" && document.querySelector(".theme-toggle")) {
|
||||
render(<ThemeToggle />, document.querySelector(".theme-toggle"));
|
||||
|
@ -3,17 +3,6 @@ const storageKey = "dark_mode";
|
||||
export const getDarkPref = () => localStorage.getItem(storageKey);
|
||||
export const setDarkPref = (pref) => localStorage.setItem(storageKey, pref);
|
||||
|
||||
// use the body class as a hint to what the theme was set to outside of the button component
|
||||
// use the `<html class="...">` as a hint to what the theme was set to outside of the button component
|
||||
// there's probably (definitely) a cleaner way to do this..?
|
||||
export const isDark = () => document.body.classList?.contains("dark");
|
||||
|
||||
// sets appropriate `<body class="...">`
|
||||
export const setDarkClass = (dark) => {
|
||||
if (dark) {
|
||||
document.body.classList.add("dark");
|
||||
document.body.classList.remove("light");
|
||||
} else {
|
||||
document.body.classList.add("light");
|
||||
document.body.classList.remove("dark");
|
||||
}
|
||||
};
|
||||
export const isDark = () => document.documentElement.classList.contains("dark");
|
||||
|
@ -1,25 +1,18 @@
|
||||
@use "sass:map";
|
||||
|
||||
// Takes a map of CSS properties and theme keys (see below) and set both body.light and body.dark selectors.
|
||||
// Takes a map of CSS properties and theme keys (see below) and set both html.light and html.dark selectors.
|
||||
// ex. @include themes.themed((color: "text", background-color: "background-inner"));
|
||||
@mixin themed($properties) {
|
||||
// keep track of the original selector(s) calling this mixin for below
|
||||
$selectors: #{&};
|
||||
|
||||
// add corresponding body.light and body.dark root selectors
|
||||
// add corresponding html.light and html.dark root selectors
|
||||
@each $theme, $map in $themes {
|
||||
@at-root body.#{$theme} {
|
||||
// support theming root body element
|
||||
@if $selectors == "body" {
|
||||
@at-root html.#{$theme} {
|
||||
#{$selectors} {
|
||||
@each $property, $color in $properties {
|
||||
#{$property}: map.get($map, $color);
|
||||
}
|
||||
} @else {
|
||||
#{$selectors} {
|
||||
@each $property, $color in $properties {
|
||||
#{$property}: map.get($map, $color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ main {
|
||||
|
||||
// cool link underlines via messy SCSS hacking (see ../abstracts/_functions)
|
||||
@each $theme, $map in themes.$themes {
|
||||
@at-root body.#{$theme} #{&} {
|
||||
@at-root html.#{$theme} #{&} {
|
||||
background-image: functions.underline-hack(map.get($map, "links"), map.get($map, "background-inner"));
|
||||
}
|
||||
}
|
||||
|
@ -110,23 +110,25 @@ header {
|
||||
}
|
||||
|
||||
// Dark mode toggle
|
||||
&.theme-toggle button {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
margin: 0.3em -0.3em 0 1.4em;
|
||||
cursor: pointer;
|
||||
&.theme-toggle {
|
||||
margin-left: 1.4em;
|
||||
|
||||
svg {
|
||||
width: 1.56em; // 24.33px, don't ask
|
||||
height: 1.56em;
|
||||
button {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
margin: 0.3em -0.3em 0 0;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
width: 1.56em; // 24.33px, don't ask
|
||||
height: 1.56em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no margin on the first child to make more room on narrow windows (before responsiveness kicks in).
|
||||
// last child is the dark mode toggle -- margin is set directly on it in case it's hidden (if JS is disabled).
|
||||
&:first-child,
|
||||
&:last-child {
|
||||
// no margin on the first child to make more room on narrow windows (before responsiveness kicks in)
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ div.highlight {
|
||||
}
|
||||
|
||||
// Syntax Highlighting (light) - modified from Monokai Light: https://github.com/mlgill/pygments-style-monokailight
|
||||
body.light {
|
||||
html.light {
|
||||
div.highlight,
|
||||
button.copy-button,
|
||||
:not(pre) > code {
|
||||
@ -215,7 +215,7 @@ body.light {
|
||||
}
|
||||
|
||||
// Syntax Highlighting (dark) - modified from Dracula: https://github.com/dracula/pygments
|
||||
body.dark {
|
||||
html.dark {
|
||||
div.highlight,
|
||||
button.copy-button,
|
||||
:not(pre) > code {
|
||||
|
@ -200,7 +200,7 @@ div.layout-home {
|
||||
// Loop through $colors-home (see above)
|
||||
@each $id, $colors in $colors-home {
|
||||
@each $theme, $color in $colors {
|
||||
@at-root body.#{$theme} div.layout-home a##{$id} {
|
||||
@at-root html.#{$theme} div.layout-home a##{$id} {
|
||||
color: $color;
|
||||
background-image: functions.underline-hack($color, map.get(map.get(themes.$themes, $theme), "background-inner"));
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ css: |
|
||||
div#content a#octocat svg {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
body.dark div#content a#octocat svg path {
|
||||
html.dark div#content a#octocat svg path {
|
||||
fill: #d3d3d3;
|
||||
}
|
||||
draft: false
|
||||
|
@ -72,7 +72,7 @@ function optimizeHtml() {
|
||||
collapseBooleanAttributes: true,
|
||||
removeComments: true,
|
||||
minifyCSS: true,
|
||||
minifyJS: false,
|
||||
minifyJS: true,
|
||||
})
|
||||
)
|
||||
.pipe(gulp.dest(".", { overwrite: true }));
|
||||
|
@ -7,11 +7,11 @@
|
||||
{{- partial "functions/prepare-js" . -}}
|
||||
{{- partial "functions/prepare-css" . -}}
|
||||
|
||||
<html lang="{{ .Site.LanguageCode | default "en" }}">
|
||||
<html lang="{{ .Site.LanguageCode | default "en" }}" class="{{ .Site.Params.Theme.defaultTheme }}">
|
||||
<head>
|
||||
{{ partial "head/_head" . }}
|
||||
</head>
|
||||
<body class="{{ .Site.Params.Theme.defaultTheme }}">
|
||||
<body>
|
||||
{{ partialCached "page/header" . }}
|
||||
<main>
|
||||
{{ block "main" . }}{{ end }}
|
||||
|
@ -1,6 +1,7 @@
|
||||
{{ partial "head/meta" . -}}
|
||||
{{ partial "head/open-graph" . -}}
|
||||
{{ partial "head/canonical" . -}}
|
||||
{{ partialCached "head/restore-theme" . -}}
|
||||
{{ partialCached "head/preload" . -}}
|
||||
{{ partial "head/styles" . -}}
|
||||
{{ partialCached "head/favicons" . -}}
|
||||
|
6
layouts/partials/head/restore-theme.html
Normal file
6
layouts/partials/head/restore-theme.html
Normal file
@ -0,0 +1,6 @@
|
||||
{{ $themeScript := resources.Get "js/restore-theme.js" | js.Build (dict "target" "es2015" "format" "iife") }}
|
||||
{{ with $themeScript }}
|
||||
<script>
|
||||
{{ .Content | safeJS }}
|
||||
</script>
|
||||
{{ end }}
|
Loading…
x
Reference in New Issue
Block a user