/** * Kanbante — sidebar behaviour (issue #16, 0.16.0 layout). * Desktop: collapse to an icon rail (persisted in localStorage). * Mobile: the same sidebar becomes an off-canvas drawer, toggled * by the topbar burger, closed by backdrop / Escape / navigation. */ (function () { var burger = document.getElementById('nav-burger'); var backdrop = document.getElementById('side-backdrop'); var collapseBtn = document.getElementById('side-collapse'); function closeDrawer() { document.body.classList.remove('sidebar-open'); if (backdrop) backdrop.hidden = true; if (burger) { burger.setAttribute('aria-expanded', 'false'); burger.textContent = '☰'; } } if (burger) { burger.addEventListener('click', function () { var open = document.body.classList.toggle('sidebar-open'); burger.setAttribute('aria-expanded', open ? 'true' : 'false'); burger.textContent = open ? '✕' : '☰'; if (backdrop) backdrop.hidden = !open; }); } if (backdrop) backdrop.addEventListener('click', closeDrawer); document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeDrawer(); }); window.addEventListener('resize', function () { if (window.innerWidth > 900) closeDrawer(); }); /* Bascule Dark/Light (#52) : le choix est mémorisé et l'amorce de _header.html l'a déjà appliqué avant le rendu. Ici, on ne fait que synchroniser le libellé et le clic. */ var themeBtn = document.getElementById('theme-toggle'); function syncThemeBtn(theme) { if (!themeBtn) return; var light = theme === 'light'; /* Bouton dans la section Administration (#55) : icône et libellé sont des séparés ; l'ancien bouton de l'en-tête, lui, n'avait qu'un texte. */ var ico = themeBtn.querySelector('[data-theme-ico]'); var lbl = themeBtn.querySelector('[data-theme-label]'); if (ico) ico.textContent = light ? '☾' : '☀'; if (lbl) lbl.textContent = light ? 'Mode sombre' : 'Mode clair'; if (!ico && !lbl) { themeBtn.textContent = light ? '☾ Mode sombre' : '☀ Mode clair'; } themeBtn.setAttribute('aria-pressed', light ? 'true' : 'false'); } if (themeBtn) { syncThemeBtn(document.documentElement.getAttribute('data-theme') || 'dark'); themeBtn.addEventListener('click', function () { var next = document.documentElement.getAttribute('data-theme') === 'light' ? 'dark' : 'light'; document.documentElement.setAttribute('data-theme', next); try { localStorage.setItem('kanbante-theme', next); } catch (e) { /* non-fatal */ } syncThemeBtn(next); }); } /* Thème commun de la suite (HUB-18, fusion #132) : la barre commune (v0.5.0) applique la préférence du Hub à l'ouverture — directement sur data-theme depuis son cache, et via l'événement « draco:theme » quand le fetch aboutit. Ici, on resynchronise le bouton de bascule avec le mode effectif. Le choix local « kanbante-theme » n'est PAS écrasé : la préférence du Hub initialise l'ouverture, le bouton reste le maître pour la session (son clic mémorise comme avant). Repli : Hub muet → aucun événement, l'amorce de _header.html (choix local, défaut sombre) reste seule maître, comme avant. La barre (defer) s'exécute avant nav.js : sa préférence déjà résolue depuis son cache est aussi reprise au démarrage. */ function appliquerThemeCommun(mode) { document.documentElement.setAttribute('data-theme', mode); syncThemeBtn(mode); } document.addEventListener('draco:theme', function (e) { var mode = e && e.detail && e.detail.mode; if (mode === 'dark' || mode === 'light') appliquerThemeCommun(mode); }); if (window.DRACO_BAR && (window.DRACO_BAR.themeMode === 'dark' || window.DRACO_BAR.themeMode === 'light')) { appliquerThemeCommun(window.DRACO_BAR.themeMode); } /* En mobile, le bouton de repli dans le tiroir FERME le tiroir : replier un tiroir de 264 px n'a pas de sens, et avant ce correctif il basculait l'état desktop (tiroir 68 px). */ var sideLinks = document.querySelectorAll('.side-link'); sideLinks.forEach(function (link) { link.addEventListener('click', function () { if (window.innerWidth <= 900) closeDrawer(); }); }); if (collapseBtn) { collapseBtn.addEventListener('click', function () { if (window.innerWidth <= 900) { closeDrawer(); return; } var collapsed = document.body.classList.toggle('sidebar-collapsed'); try { localStorage.setItem('kanbante-sidebar', collapsed ? 'collapsed' : 'open'); } catch (e) { /* private mode: state lasts for the page only */ } }); } /* Accordéon EXCLUSIF (0.22.0) : une seule section ouverte à la fois. Ouvrir une section ferme les autres ; cliquer la section ouverte la referme. L'état initial vient du script d'amorce de _header.html (section de la page courante ou choix mémorisé dans kanbante-nav-open). */ var groupBtns = document.querySelectorAll('[data-group-btn]'); function syncGroupBtn(btn) { var closed = document.body.classList.contains( 'nav-closed-' + btn.getAttribute('data-group-btn')); btn.setAttribute('aria-expanded', closed ? 'false' : 'true'); } function setOpenGroup(openKey) { groupBtns.forEach(function (btn) { var key = btn.getAttribute('data-group-btn'); document.body.classList.toggle('nav-closed-' + key, key !== openKey); syncGroupBtn(btn); }); try { localStorage.setItem('kanbante-nav-open', openKey); } catch (e) { /* non-fatal */ } } groupBtns.forEach(function (btn) { syncGroupBtn(btn); btn.addEventListener('click', function () { var key = btn.getAttribute('data-group-btn'); var isOpen = !document.body.classList.contains('nav-closed-' + key); setOpenGroup(isOpen ? '' : key); }); }); })();