fix(ui): lift theme popover out of header stacking context to appear above modals

This commit is contained in:
Will Miao
2026-06-18 22:19:36 +08:00
parent 3012a7aef3
commit a617487a43
3 changed files with 71 additions and 54 deletions

View File

@@ -329,16 +329,14 @@
.theme-popover { .theme-popover {
display: none; display: none;
position: absolute; position: fixed;
top: calc(100% + 8px);
right: -8px;
background: var(--surface-base, #ffffff); background: var(--surface-base, #ffffff);
border: 1px solid var(--border-base, #e0e0e0); border: 1px solid var(--border-base, #e0e0e0);
border-radius: var(--radius-md, 8px); border-radius: var(--radius-md, 8px);
box-shadow: var(--shadow-xl, 0 4px 16px rgba(0, 0, 0, 0.15)); box-shadow: var(--shadow-xl, 0 4px 16px rgba(0, 0, 0, 0.15));
padding: 12px; padding: 12px;
min-width: 220px; min-width: 220px;
z-index: var(--z-dropdown, 200); z-index: calc(var(--z-overlay) + 1);
animation: theme-popover-in 0.15s ease-out; animation: theme-popover-in 0.15s ease-out;
} }

View File

@@ -121,11 +121,11 @@ export class HeaderManager {
this.updatePopoverActiveStates(currentTheme, currentPreset); this.updatePopoverActiveStates(currentTheme, currentPreset);
themeToggle.addEventListener('click', (e) => { themeToggle.addEventListener('click', (e) => {
if (e.target.closest('.theme-popover')) return;
e.stopPropagation(); e.stopPropagation();
const isOpen = themePopover.classList.contains('active'); const isOpen = themePopover.classList.contains('active');
this.closeAllPopovers(); this.closeAllPopovers();
if (!isOpen) { if (!isOpen) {
this.positionThemePopover();
themePopover.classList.add('active'); themePopover.classList.add('active');
} }
}); });
@@ -149,6 +149,13 @@ export class HeaderManager {
themePopover.classList.remove('active'); themePopover.classList.remove('active');
} }
}); });
// Reposition on resize while popover is active
window.addEventListener('resize', () => {
if (themePopover.classList.contains('active')) {
this.positionThemePopover();
}
});
} }
closeAllPopovers() { closeAllPopovers() {
@@ -158,6 +165,17 @@ export class HeaderManager {
} }
} }
positionThemePopover() {
const themeToggle = document.querySelector('.theme-toggle');
const themePopover = document.getElementById('themePopover');
if (!themeToggle || !themePopover) return;
const rect = themeToggle.getBoundingClientRect();
// Guard: toggle may be hidden on narrow viewports (≤950px CSS hides .header-controls)
if (rect.width === 0 || rect.height === 0) return;
themePopover.style.top = (rect.bottom + 8) + 'px';
themePopover.style.right = (window.innerWidth - rect.right - 8) + 'px';
}
setThemeMode(mode) { setThemeMode(mode) {
setStorageItem('theme', mode); setStorageItem('theme', mode);
const htmlElement = document.documentElement; const htmlElement = document.documentElement;

View File

@@ -72,55 +72,6 @@
<i class="fas fa-moon dark-icon"></i> <i class="fas fa-moon dark-icon"></i>
<i class="fas fa-sun light-icon"></i> <i class="fas fa-sun light-icon"></i>
<i class="fas fa-adjust auto-icon"></i> <i class="fas fa-adjust auto-icon"></i>
<div class="theme-popover" id="themePopover">
<div class="theme-popover-section">
<div class="theme-popover-label">{{ t('header.theme.mode') }}</div>
<div class="theme-popover-modes">
<button class="theme-mode-btn" data-mode="light" title="{{ t('header.theme.light') }}">
<i class="fas fa-sun"></i>
<span>{{ t('header.theme.light') }}</span>
</button>
<button class="theme-mode-btn" data-mode="dark" title="{{ t('header.theme.dark') }}">
<i class="fas fa-moon"></i>
<span>{{ t('header.theme.dark') }}</span>
</button>
<button class="theme-mode-btn" data-mode="auto" title="{{ t('header.theme.auto') }}">
<i class="fas fa-adjust"></i>
<span>{{ t('header.theme.auto') }}</span>
</button>
</div>
</div>
<div class="theme-popover-divider"></div>
<div class="theme-popover-section">
<div class="theme-popover-label">{{ t('header.theme.presets') }}</div>
<div class="theme-popover-presets">
<button class="theme-preset-btn" data-preset="default" title="{{ t('header.theme.default') }}">
<span class="preset-swatch preset-swatch-default"></span>
<span>{{ t('header.theme.default') }}</span>
</button>
<button class="theme-preset-btn" data-preset="nord" title="{{ t('header.theme.nord') }}">
<span class="preset-swatch preset-swatch-nord"></span>
<span>{{ t('header.theme.nord') }}</span>
</button>
<button class="theme-preset-btn" data-preset="midnight" title="{{ t('header.theme.midnight') }}">
<span class="preset-swatch preset-swatch-midnight"></span>
<span>{{ t('header.theme.midnight') }}</span>
</button>
<button class="theme-preset-btn" data-preset="monokai" title="{{ t('header.theme.monokai') }}">
<span class="preset-swatch preset-swatch-monokai"></span>
<span>{{ t('header.theme.monokai') }}</span>
</button>
<button class="theme-preset-btn" data-preset="dracula" title="{{ t('header.theme.dracula') }}">
<span class="preset-swatch preset-swatch-dracula"></span>
<span>{{ t('header.theme.dracula') }}</span>
</button>
<button class="theme-preset-btn" data-preset="solarized" title="{{ t('header.theme.solarized') }}">
<span class="preset-swatch preset-swatch-solarized"></span>
<span>{{ t('header.theme.solarized') }}</span>
</button>
</div>
</div>
</div>
</div> </div>
<div class="settings-toggle" title="{{ t('common.actions.settings') }}"> <div class="settings-toggle" title="{{ t('common.actions.settings') }}">
<i class="fas fa-cog"></i> <i class="fas fa-cog"></i>
@@ -169,6 +120,56 @@
</div> </div>
</header> </header>
<div class="theme-popover" id="themePopover" role="dialog" aria-label="{{ t('header.theme.toggle') }}">
<div class="theme-popover-section">
<div class="theme-popover-label">{{ t('header.theme.mode') }}</div>
<div class="theme-popover-modes">
<button class="theme-mode-btn" data-mode="light" title="{{ t('header.theme.light') }}">
<i class="fas fa-sun"></i>
<span>{{ t('header.theme.light') }}</span>
</button>
<button class="theme-mode-btn" data-mode="dark" title="{{ t('header.theme.dark') }}">
<i class="fas fa-moon"></i>
<span>{{ t('header.theme.dark') }}</span>
</button>
<button class="theme-mode-btn" data-mode="auto" title="{{ t('header.theme.auto') }}">
<i class="fas fa-adjust"></i>
<span>{{ t('header.theme.auto') }}</span>
</button>
</div>
</div>
<div class="theme-popover-divider"></div>
<div class="theme-popover-section">
<div class="theme-popover-label">{{ t('header.theme.presets') }}</div>
<div class="theme-popover-presets">
<button class="theme-preset-btn" data-preset="default" title="{{ t('header.theme.default') }}">
<span class="preset-swatch preset-swatch-default"></span>
<span>{{ t('header.theme.default') }}</span>
</button>
<button class="theme-preset-btn" data-preset="nord" title="{{ t('header.theme.nord') }}">
<span class="preset-swatch preset-swatch-nord"></span>
<span>{{ t('header.theme.nord') }}</span>
</button>
<button class="theme-preset-btn" data-preset="midnight" title="{{ t('header.theme.midnight') }}">
<span class="preset-swatch preset-swatch-midnight"></span>
<span>{{ t('header.theme.midnight') }}</span>
</button>
<button class="theme-preset-btn" data-preset="monokai" title="{{ t('header.theme.monokai') }}">
<span class="preset-swatch preset-swatch-monokai"></span>
<span>{{ t('header.theme.monokai') }}</span>
</button>
<button class="theme-preset-btn" data-preset="dracula" title="{{ t('header.theme.dracula') }}">
<span class="preset-swatch preset-swatch-dracula"></span>
<span>{{ t('header.theme.dracula') }}</span>
</button>
<button class="theme-preset-btn" data-preset="solarized" title="{{ t('header.theme.solarized') }}">
<span class="preset-swatch preset-swatch-solarized"></span>
<span>{{ t('header.theme.solarized') }}</span>
</button>
</div>
</div>
</div>
<!-- Add search options panel with context-aware options --> <!-- Add search options panel with context-aware options -->
<div id="searchOptionsPanel" class="search-options-panel hidden"> <div id="searchOptionsPanel" class="search-options-panel hidden">
<div class="options-header"> <div class="options-header">