Skip to content

Commit a4da8f8

Browse files
authored
Add comprehensive dark mode support to portal (#17)
* Add comprehensive dark mode support to portal Features: - Automatic system preference detection via prefers-color-scheme - Manual toggle button (floating action button in bottom-right) - Persistent user preference stored in localStorage - Smooth color transitions Dark mode styling: - Inverted neutral color palette (#1A1A1A → #E8E8E8) - Adjusted primary colors for better contrast (#4A9EFF) - HTTP method badges with transparency overlays - Comprehensive component coverage (cards, sidebar, code blocks, tables, modals, forms) - Custom scrollbar styling JavaScript: - initDarkMode() IIFE with system preference fallback - Theme toggle with icon state updates - Guards against missing matchMedia in test environments All 240 tests passing. * Small code fixes * Cleanup
1 parent a99b062 commit a4da8f8

3 files changed

Lines changed: 310 additions & 81 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ validation-reports/
33
portal/
44
__pycache__
55
.DS_Store
6-
node_modules/
6+
node_modules/
7+
.coverage
8+
*.log

scripts/portal_generator/assets/portal.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7218,3 +7218,84 @@ function clearAllVariables(slug) {
72187218
console.log('All variables cleared for:', slug);
72197219
}
72207220

7221+
// ============================================================================
7222+
// Dark Mode Toggle
7223+
// ============================================================================
7224+
7225+
(function initDarkMode() {
7226+
// Check for saved theme preference or default to system preference
7227+
var savedTheme = localStorage.getItem('theme');
7228+
var systemPrefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
7229+
var initialTheme = savedTheme || (systemPrefersDark ? 'dark' : 'light');
7230+
7231+
// Apply initial theme
7232+
if (initialTheme === 'dark') {
7233+
document.documentElement.setAttribute('data-theme', 'dark');
7234+
}
7235+
7236+
// Create and inject dark mode toggle button
7237+
var toggleButton = document.createElement('button');
7238+
toggleButton.id = 'dark-mode-toggle';
7239+
toggleButton.className = 'dark-mode-toggle';
7240+
toggleButton.setAttribute('aria-label', 'Toggle dark mode');
7241+
toggleButton.innerHTML = '<svg class="theme-icon theme-icon-dark" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" fill="currentColor"/></svg><svg class="theme-icon theme-icon-light" width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" fill="currentColor"/></svg>';
7242+
7243+
// Add toggle button to page after DOM is ready
7244+
function addToggleButton() {
7245+
// Try to add to header if it exists, otherwise add to body
7246+
var header = document.querySelector('.ms-com-content-header') || document.querySelector('header');
7247+
if (header) {
7248+
header.appendChild(toggleButton);
7249+
} else {
7250+
document.body.appendChild(toggleButton);
7251+
}
7252+
7253+
// Add click handler
7254+
toggleButton.addEventListener('click', toggleDarkMode);
7255+
7256+
// Update button state
7257+
updateToggleButton();
7258+
}
7259+
7260+
function toggleDarkMode() {
7261+
var currentTheme = document.documentElement.getAttribute('data-theme');
7262+
var newTheme = currentTheme === 'dark' ? 'light' : 'dark';
7263+
7264+
if (newTheme === 'dark') {
7265+
document.documentElement.setAttribute('data-theme', 'dark');
7266+
} else {
7267+
document.documentElement.removeAttribute('data-theme');
7268+
}
7269+
7270+
localStorage.setItem('theme', newTheme);
7271+
updateToggleButton();
7272+
}
7273+
7274+
function updateToggleButton() {
7275+
var isDark = document.documentElement.getAttribute('data-theme') === 'dark';
7276+
toggleButton.setAttribute('aria-pressed', isDark);
7277+
}
7278+
7279+
// Initialize when DOM is ready
7280+
if (document.readyState === 'loading') {
7281+
document.addEventListener('DOMContentLoaded', addToggleButton);
7282+
} else {
7283+
addToggleButton();
7284+
}
7285+
7286+
// Listen for system theme changes
7287+
if (window.matchMedia) {
7288+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
7289+
// Only auto-switch if user hasn't manually set a preference
7290+
if (!localStorage.getItem('theme')) {
7291+
if (e.matches) {
7292+
document.documentElement.setAttribute('data-theme', 'dark');
7293+
} else {
7294+
document.documentElement.removeAttribute('data-theme');
7295+
}
7296+
updateToggleButton();
7297+
}
7298+
});
7299+
}
7300+
})();
7301+

0 commit comments

Comments
 (0)