Add theme picker dropdown with swatches for theme selection

This commit is contained in:
interlope committed 2026-05-29 18:46:39 +02:00
1 parent 142dcbdf0a
commit 1e2bcb18f1
3 files changed
+144 -22

No files matched your search

+80
View File
@@ -548,6 +548,86 @@ img.nav-svg-icon {
margin-right: 0;
}
/* ----------------------------------------
THEME PICKER DROPDOWN
---------------------------------------- */
.theme-picker-wrap {
position: relative;
}
.theme-picker {
position: absolute;
right: 0;
top: calc(100% + 10px);
display: flex;
gap: 2px;
padding: 10px 8px;
background: #111111;
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: var(--radius-md);
box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);
z-index: 1051;
/* hidden by default */
visibility: hidden;
opacity: 0;
transform: translateY(-6px) scale(0.95);
transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0.15s;
pointer-events: none;
}
.theme-picker.is-open {
visibility: visible;
opacity: 1;
transform: translateY(0) scale(1);
transition: opacity 0.15s ease, transform 0.15s ease, visibility 0s linear 0s;
pointer-events: auto;
}
.swatch {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
background: none;
border: none;
padding: 6px 7px;
cursor: pointer;
border-radius: 8px;
transition: background 0.12s;
}
.swatch:hover {
background: rgba(255, 255, 255, 0.08);
}
/* --sb = swatch background (bg-body of that theme)
--sa = swatch accent colour */
.swatch-dot {
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--sb);
border: 2px solid var(--sa);
transition: box-shadow 0.15s;
}
.swatch.is-active .swatch-dot {
box-shadow: 0 0 0 2px var(--sa), 0 0 0 4px rgba(255, 255, 255, 0.18);
}
.swatch-label {
font-size: 9px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: rgba(255, 255, 255, 0.45);
transition: color 0.12s;
}
.swatch.is-active .swatch-label {
color: rgba(255, 255, 255, 0.9);
}
/* ========================================
PROFILE HERO (Blowfish-style homepage)
======================================== */
+54 -19
View File
@@ -157,7 +157,7 @@
echo Theme::jsBootstrap();
?>
<!-- Theme rotation: light -> dark -> nord -> dracula -->
<!-- Theme picker -->
<script>
(function () {
var STORAGE_KEY = 'blowdit-theme';
@@ -169,8 +169,9 @@
dracula: 'bi-droplet',
catppuccin: 'bi-cup'
};
var root = document.documentElement;
var btn = document.getElementById('theme-toggle');
var root = document.documentElement;
var btn = document.getElementById('theme-toggle');
var picker = document.getElementById('theme-picker');
function syncIcon(theme) {
if (!btn) return;
@@ -179,38 +180,72 @@
icon.className = 'bi ' + (ICONS[theme] || 'bi-circle-half');
}
function syncSwatches(theme) {
if (!picker) return;
Array.prototype.forEach.call(picker.querySelectorAll('.swatch'), function (s) {
s.classList.toggle('is-active', s.getAttribute('data-pick') === theme);
});
}
function apply(theme) {
root.setAttribute('data-theme', theme);
try { localStorage.setItem(STORAGE_KEY, theme); } catch (e) {}
// Persist to a cookie so PHP pre-colours the next page server-side
// (prevents the white flash between navigations).
document.cookie = 'blowdit-theme=' + theme + '; path=/; max-age=31536000; SameSite=Lax';
// Keep the inline html background/color-scheme in sync.
var bg = window.BLOWDIT_THEME_BG || {};
if (bg[theme]) { root.style.backgroundColor = bg[theme]; }
root.style.colorScheme = (theme === 'light') ? 'light' : 'dark';
syncIcon(theme);
syncSwatches(theme);
}
// Reflect the theme set by the inline head script.
var current = root.getAttribute('data-theme') || 'light';
if (THEMES.indexOf(current) === -1) current = 'light';
syncIcon(current);
function openPicker() {
if (!picker) return;
picker.classList.add('is-open');
if (btn) btn.setAttribute('aria-expanded', 'true');
}
if (btn) {
btn.addEventListener('click', function () {
var cur = root.getAttribute('data-theme') || 'light';
var i = THEMES.indexOf(cur);
apply(THEMES[(i + 1) % THEMES.length]);
function closePicker() {
if (!picker) return;
picker.classList.remove('is-open');
if (btn) btn.setAttribute('aria-expanded', 'false');
}
// Swatch clicks
if (picker) {
Array.prototype.forEach.call(picker.querySelectorAll('.swatch'), function (s) {
s.addEventListener('click', function () {
apply(s.getAttribute('data-pick'));
closePicker();
});
});
}
// Follow the OS preference unless the visitor chose explicitly.
// Toggle button: open / close picker
if (btn) {
btn.addEventListener('click', function (e) {
e.stopPropagation();
picker && picker.classList.contains('is-open') ? closePicker() : openPicker();
});
}
// Close on outside click
document.addEventListener('click', function (e) {
if (!picker || !picker.classList.contains('is-open')) return;
if (btn && btn.contains(e.target)) return;
if (picker.contains(e.target)) return;
closePicker();
});
// Initialise icons + active swatch
var current = root.getAttribute('data-theme') || 'light';
if (THEMES.indexOf(current) === -1) current = 'light';
syncIcon(current);
syncSwatches(current);
// Follow OS preference unless the visitor chose explicitly
if (window.matchMedia) {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function (e) {
try {
if (localStorage.getItem(STORAGE_KEY)) return;
} catch (err) {}
try { if (localStorage.getItem(STORAGE_KEY)) return; } catch (err) {}
apply(e.matches ? 'dark' : 'light');
});
}
+10 -3
View File
@@ -33,11 +33,18 @@
</li>
<?php endforeach; ?>
<!-- Light / Dark mode toggle -->
<li class="nav-item ml-md-2 mt-2 mt-md-0">
<button type="button" id="theme-toggle" class="theme-toggle" aria-label="<?php echo $L->get('Toggle theme') ?>" title="<?php echo $L->get('Toggle theme') ?>">
<!-- Theme picker -->
<li class="nav-item ml-md-2 mt-2 mt-md-0 theme-picker-wrap">
<button type="button" id="theme-toggle" class="theme-toggle" aria-label="<?php echo $L->get('Toggle theme') ?>" title="<?php echo $L->get('Toggle theme') ?>" aria-haspopup="true" aria-expanded="false">
<i class="bi bi-moon-stars" aria-hidden="true"></i>
</button>
<div id="theme-picker" class="theme-picker" role="menu">
<button class="swatch" data-pick="light" title="Light" style="--sb:#ffffff;--sa:#1f1f1f"><span class="swatch-dot"></span><span class="swatch-label">Light</span></button>
<button class="swatch" data-pick="dark" title="Dark" style="--sb:#171717;--sa:#e5e5e5"><span class="swatch-dot"></span><span class="swatch-label">Dark</span></button>
<button class="swatch" data-pick="nord" title="Nord" style="--sb:#2e3440;--sa:#88c0d0"><span class="swatch-dot"></span><span class="swatch-label">Nord</span></button>
<button class="swatch" data-pick="dracula" title="Dracula" style="--sb:#282a36;--sa:#bd93f9"><span class="swatch-dot"></span><span class="swatch-label">Dracula</span></button>
<button class="swatch" data-pick="catppuccin" title="Catppuccin" style="--sb:#1e1e2e;--sa:#cba6f7"><span class="swatch-dot"></span><span class="swatch-label">Cat</span></button>
</div>
</li>
</ul>