feat: add shared theme partial with cookie-based dark mode
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
$themeSection = $themeSection ?? null;
|
||||
|
||||
if ($themeSection === 'head'):
|
||||
?>
|
||||
<script>
|
||||
(function () {
|
||||
function readThemeCookie() {
|
||||
var cookieParts = document.cookie ? document.cookie.split(';') : [];
|
||||
|
||||
for (var i = 0; i < cookieParts.length; i++) {
|
||||
var part = cookieParts[i].trim();
|
||||
if (part.indexOf('theme=') === 0) {
|
||||
var value = decodeURIComponent(part.substring(6));
|
||||
if (value === 'dark' || value === 'light') {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveTheme() {
|
||||
var cookieTheme = readThemeCookie();
|
||||
if (cookieTheme) {
|
||||
return cookieTheme;
|
||||
}
|
||||
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
return 'dark';
|
||||
}
|
||||
|
||||
return 'light';
|
||||
}
|
||||
|
||||
var resolvedTheme = resolveTheme();
|
||||
var html = document.documentElement;
|
||||
|
||||
html.classList.toggle('dark', resolvedTheme === 'dark');
|
||||
html.setAttribute('data-theme', resolvedTheme);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--theme-bg: #f3f4f6;
|
||||
--theme-surface: #ffffff;
|
||||
--theme-surface-alt: #f9fafb;
|
||||
--theme-text: #111827;
|
||||
--theme-text-muted: #6b7280;
|
||||
--theme-border: #d1d5db;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
--theme-bg: #111827;
|
||||
--theme-surface: #1f2937;
|
||||
--theme-surface-alt: #111827;
|
||||
--theme-text: #f3f4f6;
|
||||
--theme-text-muted: #9ca3af;
|
||||
--theme-border: #374151;
|
||||
}
|
||||
|
||||
.theme-page {
|
||||
background-color: var(--theme-bg);
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
.theme-surface {
|
||||
background-color: var(--theme-surface);
|
||||
}
|
||||
|
||||
.theme-surface-alt {
|
||||
background-color: var(--theme-surface-alt);
|
||||
}
|
||||
|
||||
.theme-text {
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
.theme-text-muted {
|
||||
color: var(--theme-text-muted);
|
||||
}
|
||||
|
||||
.theme-border {
|
||||
border-color: var(--theme-border);
|
||||
}
|
||||
|
||||
.theme-input {
|
||||
background-color: var(--theme-surface);
|
||||
border-color: var(--theme-border);
|
||||
color: var(--theme-text);
|
||||
}
|
||||
|
||||
.theme-input::placeholder {
|
||||
color: var(--theme-text-muted);
|
||||
}
|
||||
|
||||
#themeToggle {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
#themeToggle:focus-visible {
|
||||
outline: 3px solid #2563eb;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
</style>
|
||||
<?php
|
||||
endif;
|
||||
|
||||
if ($themeSection === 'body'):
|
||||
?>
|
||||
<button
|
||||
id="themeToggle"
|
||||
type="button"
|
||||
class="bg-blue-600 hover:bg-blue-700 text-white p-3 rounded-full shadow-md transition"
|
||||
aria-label="Switch to dark mode"
|
||||
title="Switch to dark mode"
|
||||
>
|
||||
<i id="themeToggleIcon" class="bi bi-moon-stars-fill" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
||||
<noscript>
|
||||
<style>
|
||||
#themeToggle {
|
||||
display: none !important;
|
||||
}
|
||||
</style>
|
||||
</noscript>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var html = document.documentElement;
|
||||
var toggleButton = document.getElementById('themeToggle');
|
||||
var toggleIcon = document.getElementById('themeToggleIcon');
|
||||
|
||||
if (!toggleButton || !toggleIcon) {
|
||||
return;
|
||||
}
|
||||
|
||||
function readThemeCookie() {
|
||||
var cookieParts = document.cookie ? document.cookie.split(';') : [];
|
||||
|
||||
for (var i = 0; i < cookieParts.length; i++) {
|
||||
var part = cookieParts[i].trim();
|
||||
if (part.indexOf('theme=') === 0) {
|
||||
var value = decodeURIComponent(part.substring(6));
|
||||
if (value === 'dark' || value === 'light') {
|
||||
return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function writeThemeCookie(theme) {
|
||||
document.cookie = 'theme=' + encodeURIComponent(theme) + '; max-age=31536000; path=/; SameSite=Lax';
|
||||
}
|
||||
|
||||
function applyTheme(theme) {
|
||||
var validTheme = theme === 'dark' ? 'dark' : 'light';
|
||||
html.classList.toggle('dark', validTheme === 'dark');
|
||||
html.setAttribute('data-theme', validTheme);
|
||||
return validTheme;
|
||||
}
|
||||
|
||||
function syncToggle(theme) {
|
||||
var isDark = theme === 'dark';
|
||||
var label = isDark ? 'Switch to light mode' : 'Switch to dark mode';
|
||||
var iconClass = isDark ? 'bi-sun-fill' : 'bi-moon-stars-fill';
|
||||
|
||||
toggleButton.setAttribute('aria-label', label);
|
||||
toggleButton.setAttribute('title', label);
|
||||
toggleIcon.className = 'bi ' + iconClass;
|
||||
}
|
||||
|
||||
var initialTheme;
|
||||
if (html.classList.contains('dark')) {
|
||||
initialTheme = 'dark';
|
||||
} else {
|
||||
initialTheme = readThemeCookie() || 'light';
|
||||
}
|
||||
|
||||
var currentTheme = applyTheme(initialTheme);
|
||||
syncToggle(currentTheme);
|
||||
|
||||
toggleButton.addEventListener('click', function () {
|
||||
var nextTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||
|
||||
currentTheme = applyTheme(nextTheme);
|
||||
|
||||
try {
|
||||
writeThemeCookie(currentTheme);
|
||||
} catch (error) {
|
||||
// Visual switch remains active if cookie storage is restricted.
|
||||
}
|
||||
|
||||
syncToggle(currentTheme);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
endif;
|
||||
Reference in New Issue
Block a user