feat: add dark mode support v2

This commit is contained in:
2026-03-04 11:31:40 -08:00
parent e05dfef5ab
commit 969a690732
3 changed files with 110 additions and 9 deletions

View File

@@ -1,3 +1,63 @@
// ============ THEME ============
const THEME_STORAGE_KEY = 'agentdash-theme';
const themeToggleBtn = document.getElementById('theme-toggle');
const systemThemeMedia = window.matchMedia('(prefers-color-scheme: dark)');
function getSystemTheme() {
return systemThemeMedia.matches ? 'dark' : 'light';
}
function getSavedTheme() {
try {
const saved = localStorage.getItem(THEME_STORAGE_KEY);
return saved === 'light' || saved === 'dark' ? saved : null;
} catch {
return null;
}
}
function setSavedTheme(theme) {
try {
localStorage.setItem(THEME_STORAGE_KEY, theme);
} catch {
// Ignore localStorage errors (privacy mode, quota, etc.).
}
}
function updateThemeToggleLabel() {
if (!themeToggleBtn) return;
const isDarkTheme = document.documentElement.getAttribute('data-theme') === 'dark';
const nextTheme = isDarkTheme ? 'light' : 'dark';
themeToggleBtn.textContent = isDarkTheme ? 'Light Mode' : 'Dark Mode';
themeToggleBtn.setAttribute('aria-label', `Switch to ${nextTheme} mode`);
}
function applyTheme(theme, { persist = false } = {}) {
document.documentElement.setAttribute('data-theme', theme);
if (persist) setSavedTheme(theme);
updateThemeToggleLabel();
if (usageStats) renderUsageCharts();
}
function initTheme() {
const savedTheme = getSavedTheme();
applyTheme(savedTheme || getSystemTheme());
if (themeToggleBtn) {
themeToggleBtn.addEventListener('click', () => {
const currentTheme = document.documentElement.getAttribute('data-theme') || getSystemTheme();
const nextTheme = currentTheme === 'dark' ? 'light' : 'dark';
applyTheme(nextTheme, { persist: true });
});
}
systemThemeMedia.addEventListener('change', (event) => {
if (!getSavedTheme()) {
applyTheme(event.matches ? 'dark' : 'light');
}
});
}
// ============ NAVIGATION ============ // ============ NAVIGATION ============
const navLinks = document.querySelectorAll('.nav-link'); const navLinks = document.querySelectorAll('.nav-link');
const pages = document.querySelectorAll('.page'); const pages = document.querySelectorAll('.page');
@@ -559,6 +619,11 @@ function renderUsageStats() {
} }
function renderUsageCharts() { function renderUsageCharts() {
const rootStyles = getComputedStyle(document.documentElement);
const themeForeground = rootStyles.getPropertyValue('--fg').trim() || '#e0e0e0';
const themeBorder = rootStyles.getPropertyValue('--border').trim() || '#444';
const themePrimary = rootStyles.getPropertyValue('--primary').trim() || '#3498db';
// Provider chart // Provider chart
const providerCtx = document.getElementById('chart-provider').getContext('2d'); const providerCtx = document.getElementById('chart-provider').getContext('2d');
@@ -583,7 +648,7 @@ function renderUsageCharts() {
plugins: { plugins: {
legend: { legend: {
position: 'bottom', position: 'bottom',
labels: { color: '#e0e0e0' } labels: { color: themeForeground }
} }
} }
} }
@@ -604,7 +669,7 @@ function renderUsageCharts() {
datasets: [{ datasets: [{
label: 'Requests', label: 'Requests',
data: agentData, data: agentData,
backgroundColor: '#3498db' backgroundColor: themePrimary
}] }]
}, },
options: { options: {
@@ -617,12 +682,12 @@ function renderUsageCharts() {
scales: { scales: {
y: { y: {
beginAtZero: true, beginAtZero: true,
ticks: { color: '#e0e0e0' }, ticks: { color: themeForeground },
grid: { color: '#444' } grid: { color: themeBorder }
}, },
x: { x: {
ticks: { color: '#e0e0e0' }, ticks: { color: themeForeground },
grid: { color: '#444' } grid: { color: themeBorder }
} }
} }
} }
@@ -708,6 +773,7 @@ function escapeHtml(text) {
// ============ INITIALIZATION ============ // ============ INITIALIZATION ============
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
initTheme();
populateAgentDropdown(); populateAgentDropdown();
loadTasks(); loadTasks();

View File

@@ -19,6 +19,7 @@
<a href="#wiki" class="nav-link" data-page="wiki">Wiki</a> <a href="#wiki" class="nav-link" data-page="wiki">Wiki</a>
<a href="#agents" class="nav-link" data-page="agents">Agents</a> <a href="#agents" class="nav-link" data-page="agents">Agents</a>
<a href="#usage" class="nav-link" data-page="usage">Usage</a> <a href="#usage" class="nav-link" data-page="usage">Usage</a>
<button id="theme-toggle" class="btn-secondary" type="button" aria-label="Toggle theme">Dark Mode</button>
</nav> </nav>
</header> </header>

View File

@@ -1,11 +1,22 @@
:root { :root {
--bg: #1a1a1a; --bg: #f5f7fb;
--fg: #e0e0e0; --fg: #1f2937;
--border: #444; --border: #c8d1df;
--primary: #3498db; --primary: #3498db;
--secondary: #2ecc71; --secondary: #2ecc71;
--danger: #e74c3c; --danger: #e74c3c;
--warning: #f39c12; --warning: #f39c12;
--dark: #1f2937;
--light: #f8fafc;
--card-bg: #ffffff;
--card-fg: #111827;
--modal-bg: rgba(0, 0, 0, 0.45);
}
:root[data-theme='dark'] {
--bg: #1a1a1a;
--fg: #e0e0e0;
--border: #444;
--dark: #121212; --dark: #121212;
--light: #f0f0f0; --light: #f0f0f0;
--card-bg: #2a2a2a; --card-bg: #2a2a2a;
@@ -13,6 +24,19 @@
--modal-bg: rgba(0, 0, 0, 0.7); --modal-bg: rgba(0, 0, 0, 0.7);
} }
@media (prefers-color-scheme: dark) {
:root:not([data-theme='light']) {
--bg: #1a1a1a;
--fg: #e0e0e0;
--border: #444;
--dark: #121212;
--light: #f0f0f0;
--card-bg: #2a2a2a;
--card-fg: #e0e0e0;
--modal-bg: rgba(0, 0, 0, 0.7);
}
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@@ -24,6 +48,11 @@ body {
background-color: var(--bg); background-color: var(--bg);
color: var(--fg); color: var(--fg);
line-height: 1.6; line-height: 1.6;
transition: background-color 0.3s ease, color 0.3s ease;
}
body * {
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, box-shadow 0.3s ease;
} }
.container { .container {
@@ -48,6 +77,7 @@ header h1 {
nav { nav {
display: flex; display: flex;
gap: 15px; gap: 15px;
align-items: center;
} }
.nav-link { .nav-link {
@@ -67,6 +97,10 @@ nav {
color: white; color: white;
} }
#theme-toggle {
margin-left: auto;
}
/* Buttons */ /* Buttons */
.btn-primary { .btn-primary {
background-color: var(--primary); background-color: var(--primary);