refactor(settings): implement macOS Settings style for settings modal

- Reorganize settings into 4 sections: General, Interface, Download, Advanced
- Implement section switching instead of scrolling (macOS Settings style)
- Remove collapsible/expandable sections and redundant 'SETTINGS' label
- Add accent-colored underline for section headers
- Update navigation with larger, more prominent active state
- Add fade-in animation for section transitions
- Update search to auto-switch to matching section
- Refactor CSS: 800x600 fixed modal size, remove collapse styles
- Refactor JS: simplify navigation logic, remove scroll spy and collapse code

Refs: Phase 0 settings modal optimization
This commit is contained in:
Will Miao
2026-02-24 07:19:32 +08:00
parent 3f0227ba9d
commit a221682a0d
5 changed files with 1112 additions and 1069 deletions

View File

@@ -1,4 +1,4 @@
/* Settings styles */
/* Settings Modal - macOS Settings Style */
.settings-toggle {
width: 36px;
height: 36px;
@@ -20,9 +20,10 @@
}
.settings-modal {
max-width: 950px;
width: 90vw;
max-height: 85vh;
width: 800px;
height: 600px;
max-width: 95vw;
max-height: 90vh;
display: flex;
flex-direction: column;
}
@@ -49,17 +50,6 @@
background: rgba(255, 255, 255, 0.02);
}
.settings-nav-title {
font-size: 0.85em;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-color);
opacity: 0.6;
margin-bottom: var(--space-2);
padding: 0 var(--space-1);
}
.settings-nav-list {
list-style: none;
padding: 0;
@@ -70,28 +60,30 @@
margin-bottom: var(--space-2);
}
/* Hide group titles - we use flat navigation */
.settings-nav-group-title {
font-size: 0.8em;
font-weight: 500;
color: var(--text-color);
opacity: 0.8;
padding: var(--space-1) var(--space-1);
margin-bottom: var(--space-1);
display: none;
}
/* Hide settings title */
.settings-nav-title {
display: none;
}
.settings-nav-item {
display: block;
width: 100%;
padding: 8px 12px;
padding: 10px 14px;
border: none;
background: transparent;
color: var(--text-color);
text-align: left;
font-size: 0.9em;
font-size: 14px;
font-weight: 500;
cursor: pointer;
border-radius: var(--border-radius-xs);
transition: all 0.2s ease;
margin-bottom: 2px;
margin-bottom: 4px;
}
.settings-nav-item:hover {
@@ -102,7 +94,7 @@
.settings-nav-item.active {
background: var(--lora-accent);
color: white;
font-weight: 500;
font-weight: 600;
}
/* Content Area */
@@ -560,71 +552,46 @@
padding: 6px 0;
}
/* Settings Styles */
/* Settings Section - macOS Settings Style */
.settings-section {
margin-top: var(--space-3);
border-top: 1px solid var(--lora-border);
padding-top: var(--space-2);
display: none;
animation: fadeIn 0.2s ease-out;
}
.settings-section.active {
display: block;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateX(10px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.settings-section-header {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
user-select: none;
padding: var(--space-1) 0;
margin-bottom: var(--space-2);
transition: opacity 0.2s ease;
}
.settings-section-header:hover {
opacity: 0.8;
padding: 0 0 var(--space-2) 0;
margin-bottom: var(--space-3);
border-bottom: 2px solid var(--lora-accent);
}
.settings-section-header h3 {
font-size: 1.1em;
font-size: 20px;
font-weight: 600;
margin: 0;
color: var(--text-color);
opacity: 0.9;
}
/* Remove toggle button styles */
.settings-section-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border: none;
background: transparent;
color: var(--text-color);
cursor: pointer;
transition: transform 0.3s ease;
opacity: 0.6;
}
.settings-section-toggle:hover {
opacity: 1;
}
.settings-section-toggle .chevron {
transition: transform 0.3s ease;
}
.settings-section.collapsed .settings-section-toggle .chevron {
transform: rotate(-90deg);
}
.settings-section-content {
overflow: hidden;
transition: max-height 0.3s ease, opacity 0.3s ease;
max-height: 2000px;
opacity: 1;
}
.settings-section.collapsed .settings-section-content {
max-height: 0;
opacity: 0;
display: none;
}
.setting-item {

View File

@@ -371,132 +371,39 @@ export class SettingsManager {
}
initializeNavigation() {
const settingsContent = document.querySelector('.settings-content');
const navItems = document.querySelectorAll('.settings-nav-item');
const sections = document.querySelectorAll('.settings-section');
if (!settingsContent || navItems.length === 0) return;
if (navItems.length === 0 || sections.length === 0) return;
// Handle navigation item clicks
// Handle navigation item clicks - macOS Settings style: show section instead of scroll
navItems.forEach(item => {
item.addEventListener('click', (e) => {
const targetId = item.dataset.target;
if (!targetId) return;
const sectionId = item.dataset.section;
if (!sectionId) return;
const targetSection = document.getElementById(targetId);
// Hide all sections
sections.forEach(section => {
section.classList.remove('active');
});
// Show target section
const targetSection = document.getElementById(`section-${sectionId}`);
if (targetSection) {
targetSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
targetSection.classList.add('active');
}
// Update active state
// Update active nav state
navItems.forEach(nav => nav.classList.remove('active'));
item.classList.add('active');
});
});
// Setup scroll spy
const sections = document.querySelectorAll('.settings-section, .setting-item[id^="section-"]');
const updateActiveNav = () => {
const scrollTop = settingsContent.scrollTop;
const contentHeight = settingsContent.clientHeight;
let currentSection = null;
let minDistance = Infinity;
sections.forEach(section => {
const sectionTop = section.offsetTop - settingsContent.offsetTop;
const distance = Math.abs(sectionTop - scrollTop);
if (distance < minDistance) {
minDistance = distance;
currentSection = section;
}
});
if (currentSection) {
const sectionId = currentSection.id;
navItems.forEach(item => {
item.classList.remove('active');
if (item.dataset.target === sectionId) {
item.classList.add('active');
}
});
}
};
// Use requestAnimationFrame for performance
let ticking = false;
settingsContent.addEventListener('scroll', () => {
if (!ticking) {
window.requestAnimationFrame(() => {
updateActiveNav();
ticking = false;
});
ticking = true;
}
});
// Initialize section collapse/expand
this.initializeSectionCollapse();
// Initial update
updateActiveNav();
}
initializeSectionCollapse() {
const sections = document.querySelectorAll('.settings-section, .setting-item[id^="section-"]');
const STORAGE_KEY = 'settingsModal_collapsedSections';
// Load collapsed state from localStorage
let collapsedSections = {};
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
collapsedSections = JSON.parse(stored);
}
} catch (e) {
console.warn('Failed to load collapsed sections state:', e);
// Show first section by default
const firstSection = sections[0];
if (firstSection) {
firstSection.classList.add('active');
}
sections.forEach(section => {
const sectionId = section.getAttribute('data-section') || section.id;
const header = section.querySelector('.settings-section-header');
const toggleBtn = section.querySelector('.settings-section-toggle');
if (!header || !toggleBtn) return;
// Apply initial collapsed state
if (collapsedSections[sectionId]) {
section.classList.add('collapsed');
}
// Handle toggle click
const toggleSection = () => {
const isCollapsed = section.classList.toggle('collapsed');
// Save state to localStorage
collapsedSections[sectionId] = isCollapsed;
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(collapsedSections));
} catch (e) {
console.warn('Failed to save collapsed sections state:', e);
}
};
// Click on header or toggle button
header.addEventListener('click', (e) => {
// Don't toggle if clicking on interactive elements within header
if (e.target.closest('a, button:not(.settings-section-toggle), input, select')) {
return;
}
toggleSection();
});
toggleBtn.addEventListener('click', (e) => {
e.stopPropagation();
toggleSection();
});
});
}
initializeSearch() {
@@ -549,7 +456,8 @@ export class SettingsManager {
}
performSearch(query) {
const sections = document.querySelectorAll('.settings-section, .setting-item[id^="section-"]');
const sections = document.querySelectorAll('.settings-section');
const navItems = document.querySelectorAll('.settings-nav-item');
const settingsForm = document.querySelector('.settings-form');
// Remove existing empty state
@@ -559,15 +467,15 @@ export class SettingsManager {
}
if (!query) {
// Reset all sections to visible and remove highlights
// Reset: remove highlights only, keep current section visible
sections.forEach(section => {
section.classList.remove('search-hidden', 'search-match');
this.removeSearchHighlights(section);
});
return;
}
const lowerQuery = query.toLowerCase();
let firstMatchSection = null;
let matchCount = 0;
sections.forEach(section => {
@@ -575,20 +483,37 @@ export class SettingsManager {
const hasMatch = sectionText.includes(lowerQuery);
if (hasMatch) {
section.classList.remove('search-hidden');
section.classList.add('search-match');
this.highlightSearchMatches(section, lowerQuery);
matchCount++;
// Expand section if it has matches
section.classList.remove('collapsed');
// Track first match to auto-switch
if (!firstMatchSection) {
firstMatchSection = section;
}
} else {
section.classList.add('search-hidden');
section.classList.remove('search-match');
this.removeSearchHighlights(section);
}
});
// Auto-switch to first matching section
if (firstMatchSection) {
const sectionId = firstMatchSection.id.replace('section-', '');
// Hide all sections
sections.forEach(section => section.classList.remove('active'));
// Show matching section
firstMatchSection.classList.add('active');
// Update nav active state
navItems.forEach(item => {
item.classList.remove('active');
if (item.dataset.section === sectionId) {
item.classList.add('active');
}
});
}
// Show empty state if no matches found
if (matchCount === 0 && settingsForm) {
const emptyState = document.createElement('div');