Skip to content

Commit 0d7b855

Browse files
authored
feat: implement collapsible sidebar groups with local storage support (#23)
1 parent b8ccf61 commit 0d7b855

3 files changed

Lines changed: 85 additions & 4 deletions

File tree

build.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,10 @@ function rootPrefix(pagePath) {
156156

157157
function buildSidebar(activePath, prefix) {
158158
return manifest.topics
159-
.map((topic) => {
159+
.map((topic, topicIndex) => {
160160
const collapsible = topic.pages.length > SIDEBAR_VISIBLE;
161+
const groupId = `sidebar-group-${topicIndex}`;
162+
const storageKey = encodeURIComponent(topic.name);
161163
// Never hide the page you're on — expand the group if it lives in the tail.
162164
const activeIdx = topic.pages.findIndex((p) => p.path === activePath);
163165
const expanded = collapsible && activeIdx >= SIDEBAR_VISIBLE;
@@ -181,8 +183,13 @@ function buildSidebar(activePath, prefix) {
181183

182184
return (
183185
` <div ${attrs}>\n` +
184-
` <p class="label">${topic.name}</p>\n` +
185-
` <ul>\n${items}\n </ul>${more}\n` +
186+
` <button type="button" class="nav-group-toggle label" aria-expanded="true" aria-controls="${groupId}" data-nav-key="${storageKey}">\n` +
187+
` <span>${topic.name}</span>\n` +
188+
` ${chevronDown("nav-group-chevron", 14)}\n` +
189+
` </button>\n` +
190+
` <div class="nav-group-content" id="${groupId}">\n` +
191+
` <ul>\n${items}\n </ul>${more}\n` +
192+
` </div>\n` +
186193
` </div>`
187194
);
188195
})

src/shell.html

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,56 @@
135135
if (e.target.closest("ol a")) setToc(false);
136136
});
137137

138+
var navStateKey = "knowledge-base:collapsed-sidebar-groups";
139+
if (document.body.classList.contains("is-home")) {
140+
try {
141+
window.localStorage.removeItem(navStateKey);
142+
} catch (e) {
143+
// No saved sidebar state exists when storage is unavailable.
144+
}
145+
}
146+
147+
function readCollapsedNavGroups() {
148+
try {
149+
var saved = JSON.parse(window.localStorage.getItem(navStateKey) || "[]");
150+
if (!Array.isArray(saved)) return [];
151+
return saved.filter(function (key, index) {
152+
return typeof key === "string" && saved.indexOf(key) === index;
153+
});
154+
} catch (e) {
155+
return [];
156+
}
157+
}
158+
function writeCollapsedNavGroups(groups) {
159+
try {
160+
window.localStorage.setItem(navStateKey, JSON.stringify(groups));
161+
} catch (e) {
162+
// The sidebar still works for this page when storage is unavailable.
163+
}
164+
}
165+
166+
var collapsedNavGroups = readCollapsedNavGroups();
167+
document.querySelectorAll(".nav-group-toggle").forEach(function (btn) {
168+
var key = btn.dataset.navKey;
169+
if (key && collapsedNavGroups.indexOf(key) !== -1) {
170+
btn.setAttribute("aria-expanded", "false");
171+
}
172+
173+
btn.addEventListener("click", function () {
174+
var open = btn.getAttribute("aria-expanded") === "true";
175+
btn.setAttribute("aria-expanded", open ? "false" : "true");
176+
177+
if (!key) return;
178+
var savedIndex = collapsedNavGroups.indexOf(key);
179+
if (open && savedIndex === -1) {
180+
collapsedNavGroups.push(key);
181+
} else if (!open && savedIndex !== -1) {
182+
collapsedNavGroups.splice(savedIndex, 1);
183+
}
184+
writeCollapsedNavGroups(collapsedNavGroups);
185+
});
186+
});
187+
138188
document.querySelectorAll(".show-more").forEach(function (btn) {
139189
btn.addEventListener("click", function () {
140190
var open = btn.getAttribute("aria-expanded") === "true";

src/styles.css

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,29 @@ a {
186186
color: var(--faint);
187187
margin: 0 0 6px 8px;
188188
}
189+
.nav-group-toggle {
190+
display: flex;
191+
align-items: center;
192+
justify-content: space-between;
193+
width: calc(100% - 8px);
194+
padding: 0;
195+
border: 0;
196+
background: none;
197+
line-height: inherit;
198+
text-align: left;
199+
cursor: pointer;
200+
}
201+
.nav-group-chevron {
202+
display: block;
203+
flex: none;
204+
transition: transform 0.2s ease;
205+
}
206+
.nav-group-toggle[aria-expanded="false"] .nav-group-chevron {
207+
transform: rotate(-90deg);
208+
}
209+
.nav-group-toggle[aria-expanded="false"] + .nav-group-content {
210+
display: none;
211+
}
189212
.nav-group ul {
190213
list-style: none;
191214
margin: 0;
@@ -843,7 +866,8 @@ footer.doc-footer {
843866
html {
844867
scroll-behavior: auto;
845868
}
846-
.sidebar {
869+
.sidebar,
870+
.nav-group-chevron {
847871
transition: none;
848872
}
849873
}

0 commit comments

Comments
 (0)