Skip to content

Commit 2e3ad93

Browse files
committed
Add table of contents navigation controls
Render table-of-contents buttons on EIP pages when page TOC data exists and open the existing page TOC inside the shared navigation modal. Keep the TOC slot stable in the mobile bottom bar and add the book icon treatment to the page TOC heading so the connection between the section and button is clear.
1 parent 24d796d commit 2e3ad93

4 files changed

Lines changed: 152 additions & 5 deletions

File tree

sass/assets/css/_nav-ui.scss

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,35 @@
4848
stroke: currentColor;
4949
}
5050

51+
.nav-ui-desktop-actions {
52+
display: inline-flex;
53+
align-items: center;
54+
gap: 0.45rem;
55+
margin-left: -0.3rem;
56+
vertical-align: middle;
57+
}
58+
59+
.nav-ui-desktop-actions .nav-ui-icon-button {
60+
width: 2.15rem;
61+
height: 2.15rem;
62+
color: var(--bs-body-color);
63+
border-color: var(--bs-border-color);
64+
background: rgba(var(--bs-body-bg-rgb), 0.82);
65+
}
66+
67+
.nav-ui-desktop-actions .nav-ui-icon-button:hover,
68+
.nav-ui-desktop-actions .nav-ui-icon-button:focus-visible,
69+
.nav-ui-desktop-actions .nav-ui-icon-button.is-active {
70+
color: var(--bs-emphasis-color);
71+
border-color: var(--bs-emphasis-color);
72+
background: rgba(var(--bs-secondary-bg-rgb), 0.82);
73+
}
74+
75+
.nav-ui-desktop-actions .nav-ui-icon-button svg {
76+
width: 1rem;
77+
height: 1rem;
78+
}
79+
5180
.site-nav .trigger.p-2 {
5281
padding: 0 !important;
5382
}
@@ -100,6 +129,11 @@
100129
text-decoration: none;
101130
}
102131

132+
.nav-ui-icon-button.is-placeholder {
133+
opacity: 0;
134+
pointer-events: none;
135+
}
136+
103137
.nav-ui-top-button {
104138
opacity: 0;
105139
pointer-events: none;
@@ -192,7 +226,8 @@
192226
text-align: center;
193227
}
194228

195-
.nav-ui-menu-list a {
229+
.nav-ui-menu-list a,
230+
.nav-ui-toc-content a {
196231
display: block;
197232
padding: 0.72rem 0.8rem;
198233
border: 1px solid var(--bs-border-color);
@@ -201,11 +236,44 @@
201236
text-decoration: none;
202237
}
203238

204-
.nav-ui-menu-list a:hover {
239+
.nav-ui-menu-list a:hover,
240+
.nav-ui-toc-content a:hover {
205241
border-color: #726E97;
206242
text-decoration: none;
207243
}
208244

245+
.nav-ui-toc-content h2 {
246+
display: none !important;
247+
}
248+
249+
.toc > .nav-ui-toc-heading {
250+
display: inline-flex;
251+
align-items: center;
252+
gap: 0.45rem;
253+
}
254+
255+
.nav-ui-toc-heading svg {
256+
width: 1.15em;
257+
height: 1.15em;
258+
color: currentColor;
259+
flex: 0 0 auto;
260+
}
261+
262+
.nav-ui-toc-content ul {
263+
margin: 0;
264+
padding-left: 0;
265+
list-style: none;
266+
}
267+
268+
.nav-ui-toc-content li + li {
269+
margin-top: 0.4rem;
270+
}
271+
272+
.nav-ui-toc-content ul ul {
273+
margin-top: 0.4rem;
274+
padding-left: 0.9rem;
275+
}
276+
209277
body.nav-ui-lock {
210278
overflow: hidden;
211279
}
@@ -242,6 +310,12 @@ body.nav-ui-lock {
242310
text-decoration: none;
243311
}
244312

313+
.nav-ui-desktop-actions .nav-ui-icon-button:hover {
314+
color: var(--bs-body-color);
315+
border-color: var(--bs-border-color);
316+
background: rgba(var(--bs-body-bg-rgb), 0.82);
317+
}
318+
245319
#search.nav-ui-search-link:hover {
246320
color: var(--bs-body-color);
247321
border-color: var(--bs-border-color);
@@ -258,6 +332,12 @@ body.nav-ui-lock {
258332
text-decoration: none;
259333
}
260334

335+
.nav-ui-desktop-actions .nav-ui-icon-button:hover {
336+
color: var(--bs-emphasis-color);
337+
border-color: var(--bs-emphasis-color);
338+
background: rgba(var(--bs-secondary-bg-rgb), 0.82);
339+
}
340+
261341
#search.nav-ui-search-link:hover {
262342
color: var(--bs-emphasis-color);
263343
border-color: var(--bs-emphasis-color);
@@ -401,7 +481,8 @@ body.nav-ui-lock {
401481
display: none !important;
402482
}
403483

404-
#search.nav-ui-search-link {
484+
#search.nav-ui-search-link,
485+
.nav-ui-desktop-actions {
405486
display: none !important;
406487
}
407488
}

static/assets/scripts/nav-ui.mjs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ let lastFocusedElement;
66
let pressedTimer;
77

88
const setActiveTool = (target) => {
9-
document.querySelectorAll('[data-nav-ui-open="menu"]').forEach((button) => {
9+
document.querySelectorAll('[data-nav-ui-open="menu"], [data-nav-ui-open="toc"]').forEach((button) => {
1010
button.classList.toggle("is-active", button.getAttribute("data-nav-ui-open") === target);
1111
});
1212
};
@@ -70,6 +70,24 @@ const buildMenuContent = () => {
7070
return list;
7171
};
7272

73+
const buildTocContent = () => {
74+
const toc = document.querySelector(".toc");
75+
const content = document.createElement("div");
76+
content.className = "nav-ui-toc-content";
77+
78+
if (toc) {
79+
content.appendChild(toc.cloneNode(true));
80+
}
81+
82+
content.addEventListener("click", (event) => {
83+
if (event.target.closest("a")) {
84+
closeModal();
85+
}
86+
});
87+
88+
return content;
89+
};
90+
7391
const triggerSearch = () => {
7492
const searchLink = document.getElementById("search");
7593

@@ -83,6 +101,11 @@ const openMenuModal = () => {
83101
openModal("Menu", buildMenuContent());
84102
};
85103

104+
const openTocModal = () => {
105+
setActiveTool("toc");
106+
openModal("Table of Contents", buildTocContent());
107+
};
108+
86109
const bindModal = () => {
87110
if (!ensureModal()) {
88111
return;
@@ -104,6 +127,8 @@ const bindActions = () => {
104127

105128
if (target === "menu") {
106129
openMenuModal();
130+
} else if (target === "toc") {
131+
openTocModal();
107132
} else if (target === "search") {
108133
triggerSearch();
109134
}

templates/base.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@
185185
<path d="M20 20l-3.5-3.5"></path>
186186
</svg>
187187
</a>
188+
{% block desktop_nav_tools %}{% endblock desktop_nav_tools %}
188189
</span>
189190
</div>
190191
</nav>
@@ -201,6 +202,13 @@
201202
<path d="M5 12l7-7 7 7"></path>
202203
</svg>
203204
</button>
205+
{% block mobile_toc_button %}
206+
<button type="button" class="nav-ui-icon-button is-placeholder" aria-label="Open table of contents" aria-hidden="true" tabindex="-1" disabled>
207+
<svg viewBox="0 0 30 30" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" aria-hidden="true">
208+
<path d="M4.5 7.5c2.9-1.3 5.8-1.5 9-0.7v15.7c-3-0.8-5.9-0.5-9 0.9zM25.5 7.5c-2.9-1.3-5.8-1.5-9-0.7v15.7c3-0.8 5.9-0.5 9 0.9zM15 6.8v16.3"></path>
209+
</svg>
210+
</button>
211+
{% endblock mobile_toc_button %}
204212
<button type="button" class="nav-ui-icon-button" data-nav-ui-open="search" aria-label="Search">
205213
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
206214
<circle cx="11" cy="11" r="7"></circle>

templates/eip.html

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,34 @@
55
{{ macros::eip_number(page=page) }}: {{ page.title }} | {{ config.title -}}
66
{% endblock title %}
77

8+
{% block desktop_nav_tools %}
9+
{% if page.toc %}
10+
<span class="nav-ui-desktop-actions">
11+
<button type="button" class="nav-ui-icon-button" data-nav-ui-open="toc" aria-label="Open table of contents">
12+
<svg viewBox="0 0 30 30" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" aria-hidden="true">
13+
<path d="M4.5 7.5c2.9-1.3 5.8-1.5 9-0.7v15.7c-3-0.8-5.9-0.5-9 0.9zM25.5 7.5c-2.9-1.3-5.8-1.5-9-0.7v15.7c3-0.8 5.9-0.5 9 0.9zM15 6.8v16.3"></path>
14+
</svg>
15+
</button>
16+
</span>
17+
{% endif %}
18+
{% endblock desktop_nav_tools %}
19+
20+
{% block mobile_toc_button %}
21+
{% if page.toc %}
22+
<button type="button" class="nav-ui-icon-button" data-nav-ui-open="toc" aria-label="Open table of contents">
23+
<svg viewBox="0 0 30 30" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" aria-hidden="true">
24+
<path d="M4.5 7.5c2.9-1.3 5.8-1.5 9-0.7v15.7c-3-0.8-5.9-0.5-9 0.9zM25.5 7.5c-2.9-1.3-5.8-1.5-9-0.7v15.7c3-0.8 5.9-0.5 9 0.9zM15 6.8v16.3"></path>
25+
</svg>
26+
</button>
27+
{% else %}
28+
<button type="button" class="nav-ui-icon-button is-placeholder" aria-label="Open table of contents" aria-hidden="true" tabindex="-1" disabled>
29+
<svg viewBox="0 0 30 30" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" aria-hidden="true">
30+
<path d="M4.5 7.5c2.9-1.3 5.8-1.5 9-0.7v15.7c-3-0.8-5.9-0.5-9 0.9zM25.5 7.5c-2.9-1.3-5.8-1.5-9-0.7v15.7c3-0.8 5.9-0.5 9 0.9zM15 6.8v16.3"></path>
31+
</svg>
32+
</button>
33+
{% endif %}
34+
{% endblock mobile_toc_button %}
35+
836

937
{% block extra_head %}
1038
<meta property="og:type" content="article" />
@@ -272,7 +300,12 @@ <h1 class="page-heading">
272300
</header>
273301

274302
<nav class="toc">
275-
<h2>Table of Contents</h2>
303+
<h2 class="nav-ui-toc-heading">
304+
Table of Contents
305+
<svg viewBox="0 0 30 30" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.4" aria-hidden="true">
306+
<path d="M4.5 7.5c2.9-1.3 5.8-1.5 9-0.7v15.7c-3-0.8-5.9-0.5-9 0.9zM25.5 7.5c-2.9-1.3-5.8-1.5-9-0.7v15.7c3-0.8 5.9-0.5 9 0.9zM15 6.8v16.3"></path>
307+
</svg>
308+
</h2>
276309
{% if page.toc %}
277310
<ul>
278311
{% for h1 in page.toc %}

0 commit comments

Comments
 (0)