Merge pull request #98 from samstorment/ThemeSwitcher

OS Independent Theme Switcher
This commit is contained in:
Viktor 2024-06-06 12:51:19 +02:00 committed by GitHub
commit bb06cc9ff3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 125 additions and 33 deletions

Binary file not shown.

View File

@ -1,6 +1,6 @@
// This sets the data-has-js attribute on the body tag to true, so we can style the page with the assumption that
// This sets the data-has-js attribute on the html tag to true, so we can style the page with the assumption that
// the browser supports JS. This is a progressive enhancement, so the page will still work without JS.
document.getElementsByTagName('body')[0].setAttribute('data-has-js', 'true');
document.documentElement.setAttribute('data-has-js', 'true');
// To prevent the filter menu from being opened when the user hits enter on the search box, we need to add a keydown
// handler to the search box that stops the event from propagating. Janky hack, but it works.

View File

@ -1,4 +1,6 @@
:root {
color-scheme: light;
--clr-bg-page: hsl(60, 42%, 95%); // $nicotine-light
--clr-bg-ui: hsl(0, 0%, 100%);
@ -26,8 +28,10 @@
--font-family-heading: serif; // $heading-fonts
}
@media (prefers-color-scheme: dark) {
:root {
@mixin dark-theme-mixin {
color-scheme: dark;
--clr-bg-page: hsl(0, 0%, 6%);
--clr-bg-ui: hsl(0, 0%, 18%);
@ -50,16 +54,22 @@
--clr-link-visited: #ffadff;
--clr-heading-link-visited: var(--clr-link-visited);
}
:root[data-theme='dark'] {
@include dark-theme-mixin;
}
// Makes theme match the user's OS preference when JS is disabled
@media (prefers-color-scheme: dark) {
:root:not([data-has-js="true"]) {
@include dark-theme-mixin;
}
}
* {
box-sizing: border-box;
}
html {
color-scheme: light dark;
}
a {
color: var(--clr-link);
}
@ -199,6 +209,9 @@ header {
color: var(--clr-text-ui);
box-shadow: 0 0 0.5ch var(--clr-shadow);
margin-bottom: 1ch;
display: flex;
align-items: center;
justify-content: space-between;
nav {
a {
@ -228,6 +241,15 @@ header {
}
}
#theme {
padding: .5ch;
display: none;
[data-has-js='true'] & {
display: block;
}
}
#complaint {
@extend .dialog;
max-width: 60ch;
@ -333,13 +355,11 @@ section.cards {
line-height: 1.6;
}
@media (prefers-color-scheme: dark) {
& {
[data-theme='dark'] & {
border: 1px solid var(--clr-border);
}
}
}
}
.positions {
box-shadow: 0 0 2px var(--clr-shadow);
@ -525,6 +545,8 @@ footer {
font-family: var(--font-family-heading);
font-weight: normal;
text-align: center;
display: flex;
justify-content: space-between;
}
#suggestions-anchor {
@ -717,7 +739,7 @@ footer {
}
@media (max-device-width: 624px) {
body[data-has-js="true"] { // This property is set via js so we can selectively enable these changes only if JS is enabled;
[data-has-js="true"] body { // This property is set via js so we can selectively enable these changes only if JS is enabled;
// This is desirable since mobile navigation is JS-driven. If JS is disabled, having a squished
// GUI is better than having no working UI.
margin: 0 !important;
@ -733,6 +755,8 @@ footer {
#mcfeast {
display: inline;
float: right;
width: 2rem;
font-size: 1rem;
}
#menu-close {

View File

@ -0,0 +1,57 @@
function getTheme() {
const theme = window.localStorage.getItem('theme');
// if a valid theme is set in localStorage, return it
if (theme === 'dark' || theme === 'light') {
return { value: theme, system: false };
}
// if matchMedia is supported and OS theme is dark
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
return { value: 'dark', system: true };
}
return { value: 'light', system: true };
}
function setTheme(value) {
if (value === 'dark' || value === 'light') {
window.localStorage.setItem('theme', value);
} else {
window.localStorage.removeItem('theme');
}
const theme = getTheme();
document.documentElement.setAttribute('data-theme', theme.value);
}
function initializeTheme() {
const themeSelect = document.getElementById('theme-select');
const theme = getTheme();
document.documentElement.setAttribute('data-theme', theme.value);
// system is selected by default in the themeSwitcher so ignore it here
if (!theme.system) {
themeSelect.value = theme.value;
}
themeSelect.addEventListener('change', e => {
setTheme(e.target.value);
});
const mql = window.matchMedia('(prefers-color-scheme: dark)');
// if someone changes their theme at the OS level we need to update
// their theme immediately if they're using their OS theme
mql.addEventListener('change', e => {
if (themeSelect.value !== 'system') return;
if (e.matches) setTheme('dark');
else setTheme('light');
});
}
initializeTheme();

View File

@ -7,4 +7,15 @@
<a href="https://memex.marginalia.nu/projects/edge/supporting.gmi">Donate</a>
<a class="extra" href="https://search.marginalia.nu/explore/random">Random</a>
</nav>
<div id="theme">
<label for="theme-select" class="screenreader-only">Color Theme</label>
<select id="theme-select">
<option value="system" selected>System</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
</select>
</div>
</header>
<!-- load this ASAP to avoid color theme flicker -->
<script src="/theme.js"></script>