119 lines
3.5 KiB
JavaScript
119 lines
3.5 KiB
JavaScript
/**
|
|
* Initializes tab navigation
|
|
* Usage:
|
|
* - Tab buttons: <button data-tab="system">System</button>
|
|
* - Tab panels: <div data-tab-panel="system">...</div>
|
|
* - Container: <div data-tabs>...</div>
|
|
* - URL param: ?tab=system
|
|
*/
|
|
export function initTabs(root = document) {
|
|
const containers = root.querySelectorAll('[data-tabs]');
|
|
|
|
containers.forEach((container) => {
|
|
if (container.dataset.tabsBound === '1') return;
|
|
container.dataset.tabsBound = '1';
|
|
|
|
const tabs = Array.from(container.querySelectorAll('[data-tab]'));
|
|
const panels = Array.from(container.querySelectorAll('[data-tab-panel]'));
|
|
|
|
if (!tabs.length || !panels.length) return;
|
|
|
|
const storageKey = container.dataset.tabsStorageKey || (container.id ? `tabs:${container.id}` : null);
|
|
const urlParamName = container.dataset.tabsParam || 'tab';
|
|
|
|
// Helper to get URL parameter
|
|
const getUrlParam = (name) => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
return params.get(name);
|
|
};
|
|
|
|
// Helper to set URL parameter
|
|
const setUrlParam = (name, value) => {
|
|
const url = new URL(window.location);
|
|
url.searchParams.set(name, value);
|
|
window.history.replaceState({}, '', url);
|
|
};
|
|
|
|
// Get initial active tab from URL param, localStorage, or data-tab-default
|
|
let activeTab = null;
|
|
const urlTab = getUrlParam(urlParamName);
|
|
if (urlTab && panels.some(p => p.dataset.tabPanel === urlTab)) {
|
|
activeTab = urlTab;
|
|
} else if (storageKey) {
|
|
try {
|
|
const stored = localStorage.getItem(storageKey);
|
|
if (stored && panels.some(p => p.dataset.tabPanel === stored)) {
|
|
activeTab = stored;
|
|
}
|
|
} catch (e) {
|
|
// localStorage not available
|
|
}
|
|
}
|
|
if (!activeTab) {
|
|
const defaultTab = container.querySelector('[data-tab-default]');
|
|
activeTab = defaultTab?.dataset.tab || tabs[0]?.dataset.tab;
|
|
}
|
|
|
|
const activateTab = (tabName) => {
|
|
tabs.forEach(tab => {
|
|
const isActive = tab.dataset.tab === tabName;
|
|
tab.classList.toggle('is-active', isActive);
|
|
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
});
|
|
|
|
panels.forEach(panel => {
|
|
const isVisible = panel.dataset.tabPanel === tabName;
|
|
panel.hidden = !isVisible;
|
|
panel.setAttribute('aria-hidden', isVisible ? 'false' : 'true');
|
|
});
|
|
|
|
// Update URL parameter
|
|
setUrlParam(urlParamName, tabName);
|
|
|
|
// Save to localStorage
|
|
if (storageKey) {
|
|
try {
|
|
localStorage.setItem(storageKey, tabName);
|
|
} catch (e) {
|
|
// localStorage not available
|
|
}
|
|
}
|
|
};
|
|
|
|
// Initialize tabs
|
|
tabs.forEach(tab => {
|
|
tab.setAttribute('role', 'tab');
|
|
tab.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
activateTab(tab.dataset.tab);
|
|
});
|
|
});
|
|
|
|
panels.forEach(panel => {
|
|
panel.setAttribute('role', 'tabpanel');
|
|
});
|
|
|
|
container.setAttribute('role', 'tablist');
|
|
|
|
// Activate initial tab
|
|
if (activeTab) {
|
|
activateTab(activeTab);
|
|
}
|
|
|
|
// Listen for popstate (back/forward navigation)
|
|
window.addEventListener('popstate', () => {
|
|
const newTab = getUrlParam(urlParamName);
|
|
if (newTab && panels.some(p => p.dataset.tabPanel === newTab)) {
|
|
activateTab(newTab);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Auto-initialize on load
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', () => initTabs());
|
|
} else {
|
|
initTabs();
|
|
}
|