feat(sidebar): add collapsible sections with localStorage persistence
Sidebar sections can now be collapsed/expanded by clicking the header. State is persisted to localStorage so sections remain open/closed across page navigation and browser sessions. Changes: - init-alpine.js: Added openSections state, toggleSection(), expandSectionForCurrentPage(), and localStorage helpers - sidebar.html: Refactored with Jinja2 macros for DRY code, added collapsible sections with CSS transitions and rotating chevron icons Features: - Click section header to toggle expand/collapse - Chevron rotates 180 degrees when expanded - Smooth CSS transitions (no extra Alpine plugins needed) - State persists in localStorage (admin_sidebar_sections key) - Default: Platform Administration open, others closed - Dashboard and Settings always visible (not collapsible) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
/**
|
||||
* Alpine.js v3 global data initialization
|
||||
* Provides theme toggle, menu controls, and page state
|
||||
* Provides theme toggle, menu controls, sidebar sections, and page state
|
||||
*/
|
||||
function data() {
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Theme (dark mode) persistence
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
function getThemeFromLocalStorage() {
|
||||
// if user already changed the theme, use it
|
||||
if (window.localStorage.getItem('dark')) {
|
||||
return JSON.parse(window.localStorage.getItem('dark'))
|
||||
}
|
||||
|
||||
// else return their preferences
|
||||
return (
|
||||
!!window.matchMedia &&
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
@@ -20,12 +20,74 @@ function data() {
|
||||
window.localStorage.setItem('dark', value)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Sidebar sections persistence
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
const SIDEBAR_STORAGE_KEY = 'admin_sidebar_sections';
|
||||
|
||||
// Default state: Platform Administration open, others closed
|
||||
const defaultSections = {
|
||||
platformAdmin: true,
|
||||
contentMgmt: false,
|
||||
devTools: false,
|
||||
monitoring: false
|
||||
};
|
||||
|
||||
function getSidebarSectionsFromStorage() {
|
||||
try {
|
||||
const stored = window.localStorage.getItem(SIDEBAR_STORAGE_KEY);
|
||||
if (stored) {
|
||||
return { ...defaultSections, ...JSON.parse(stored) };
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse sidebar sections from localStorage:', e);
|
||||
}
|
||||
return { ...defaultSections };
|
||||
}
|
||||
|
||||
function saveSidebarSectionsToStorage(sections) {
|
||||
try {
|
||||
window.localStorage.setItem(SIDEBAR_STORAGE_KEY, JSON.stringify(sections));
|
||||
} catch (e) {
|
||||
console.warn('Failed to save sidebar sections to localStorage:', e);
|
||||
}
|
||||
}
|
||||
|
||||
// Map pages to their parent sections
|
||||
const pageSectionMap = {
|
||||
// Platform Administration
|
||||
companies: 'platformAdmin',
|
||||
vendors: 'platformAdmin',
|
||||
users: 'platformAdmin',
|
||||
customers: 'platformAdmin',
|
||||
marketplace: 'platformAdmin',
|
||||
// Content Management
|
||||
'platform-homepage': 'contentMgmt',
|
||||
'content-pages': 'contentMgmt',
|
||||
'vendor-theme': 'contentMgmt',
|
||||
// Developer Tools
|
||||
components: 'devTools',
|
||||
icons: 'devTools',
|
||||
testing: 'devTools',
|
||||
'code-quality': 'devTools',
|
||||
// Platform Monitoring
|
||||
imports: 'monitoring',
|
||||
logs: 'monitoring'
|
||||
};
|
||||
|
||||
return {
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Theme
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
dark: getThemeFromLocalStorage(),
|
||||
toggleTheme() {
|
||||
this.dark = !this.dark
|
||||
setThemeToLocalStorage(this.dark)
|
||||
},
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Mobile side menu
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
isSideMenuOpen: false,
|
||||
toggleSideMenu() {
|
||||
this.isSideMenuOpen = !this.isSideMenuOpen
|
||||
@@ -33,6 +95,10 @@ function data() {
|
||||
closeSideMenu() {
|
||||
this.isSideMenuOpen = false
|
||||
},
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Notifications menu
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
isNotificationsMenuOpen: false,
|
||||
toggleNotificationsMenu() {
|
||||
this.isNotificationsMenuOpen = !this.isNotificationsMenuOpen
|
||||
@@ -40,6 +106,10 @@ function data() {
|
||||
closeNotificationsMenu() {
|
||||
this.isNotificationsMenuOpen = false
|
||||
},
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Profile menu
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
isProfileMenuOpen: false,
|
||||
toggleProfileMenu() {
|
||||
this.isProfileMenuOpen = !this.isProfileMenuOpen
|
||||
@@ -47,11 +117,37 @@ function data() {
|
||||
closeProfileMenu() {
|
||||
this.isProfileMenuOpen = false
|
||||
},
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Pages menu (legacy)
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
isPagesMenuOpen: false,
|
||||
togglePagesMenu() {
|
||||
this.isPagesMenuOpen = !this.isPagesMenuOpen
|
||||
},
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Collapsible sidebar sections
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
openSections: getSidebarSectionsFromStorage(),
|
||||
|
||||
toggleSection(section) {
|
||||
this.openSections[section] = !this.openSections[section];
|
||||
saveSidebarSectionsToStorage(this.openSections);
|
||||
},
|
||||
|
||||
// Auto-expand section containing current page
|
||||
expandSectionForCurrentPage() {
|
||||
const section = pageSectionMap[this.currentPage];
|
||||
if (section && !this.openSections[section]) {
|
||||
this.openSections[section] = true;
|
||||
saveSidebarSectionsToStorage(this.openSections);
|
||||
}
|
||||
},
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
// Page identifier - will be set by individual pages
|
||||
// ─────────────────────────────────────────────────────────────────
|
||||
currentPage: ''
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user