Skip to content

Commit 18fe8f0

Browse files
Migrate from Furo to unified PyData theme (#77)
* Migrate to pydata Sphinx theme * Expandable left-hand nav TOC based on subpages
1 parent d20b7c7 commit 18fe8f0

5 files changed

Lines changed: 312 additions & 21 deletions

File tree

.devcontainer/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM bitnami/python:3.13
1+
FROM python:3.14
22

33
RUN apt update && apt upgrade -y && apt install vim -y
44

docs/_templates/globaltoc.html

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
<h3><a href="{{ pathto(root_doc)|e }}">{{ project|title }}</a></h3>
2+
<div id="salt-globaltoc">
3+
{{ toctree(includehidden=True, collapse=False, maxdepth=2) }}
4+
</div>
5+
6+
<style>
7+
#salt-globaltoc {
8+
font-size: 0.875rem;
9+
}
10+
11+
#salt-globaltoc ul {
12+
list-style: none;
13+
padding: 0;
14+
margin: 0 0 0.25rem 0;
15+
}
16+
17+
/* Indent nested levels */
18+
#salt-globaltoc ul ul {
19+
padding-left: 0.75rem;
20+
margin: 0;
21+
width: 100%;
22+
}
23+
24+
#salt-globaltoc p.caption {
25+
display: flex;
26+
align-items: center;
27+
gap: 0.4em;
28+
cursor: pointer;
29+
font-size: 0.75rem;
30+
font-weight: 700;
31+
text-transform: uppercase;
32+
letter-spacing: 0.08em;
33+
color: var(--pst-color-text-muted, #6c757d);
34+
padding: 0.35rem 0.25rem 0.35rem 0;
35+
margin: 0.75rem 0 0 0;
36+
user-select: none;
37+
}
38+
39+
#salt-globaltoc p.caption:hover {
40+
color: var(--pst-color-text-base, inherit);
41+
}
42+
43+
/* Triangle arrow indicator on captions */
44+
#salt-globaltoc p.caption::before {
45+
content: "";
46+
display: inline-block;
47+
flex-shrink: 0;
48+
width: 0;
49+
height: 0;
50+
border-style: solid;
51+
border-width: 4px 0 4px 6px;
52+
border-color: transparent transparent transparent currentColor;
53+
transition: transform 0.15s ease;
54+
}
55+
56+
#salt-globaltoc p.caption.expanded::before {
57+
transform: rotate(90deg);
58+
}
59+
60+
/* Flex layout for li items that have children, so link + toggle button sit on one row */
61+
#salt-globaltoc li.has-children {
62+
display: flex;
63+
flex-wrap: wrap;
64+
align-items: flex-start;
65+
}
66+
67+
#salt-globaltoc li.has-children > a {
68+
flex: 1;
69+
min-width: 0;
70+
}
71+
72+
#salt-globaltoc li.has-children > ul {
73+
width: 100%;
74+
}
75+
76+
/* Toggle button injected next to links that have children */
77+
#salt-globaltoc .toc-toggle {
78+
flex-shrink: 0;
79+
background: none;
80+
border: none;
81+
cursor: pointer;
82+
padding: 0.25rem 0.35rem;
83+
color: var(--pst-color-text-muted, #6c757d);
84+
line-height: 1;
85+
border-radius: 4px;
86+
}
87+
88+
#salt-globaltoc .toc-toggle:hover {
89+
color: var(--pst-color-text-base, inherit);
90+
background-color: var(--pst-color-surface, rgba(0, 0, 0, 0.06));
91+
}
92+
93+
#salt-globaltoc .toc-toggle::before {
94+
content: "";
95+
display: inline-block;
96+
width: 0;
97+
height: 0;
98+
border-style: solid;
99+
border-width: 4px 0 4px 6px;
100+
border-color: transparent transparent transparent currentColor;
101+
transition: transform 0.15s ease;
102+
}
103+
104+
#salt-globaltoc .toc-toggle.expanded::before {
105+
transform: rotate(90deg);
106+
}
107+
108+
/* Link styles for all toctree levels */
109+
#salt-globaltoc li > a {
110+
display: block;
111+
padding: 0.25rem 0.5rem;
112+
color: var(--pst-color-text-base, inherit);
113+
text-decoration: none;
114+
border-radius: 4px;
115+
line-height: 1.4;
116+
}
117+
118+
#salt-globaltoc li > a:hover {
119+
background-color: var(--pst-color-surface, rgba(0, 0, 0, 0.06));
120+
}
121+
122+
#salt-globaltoc li.current > a,
123+
#salt-globaltoc li.current > a:hover {
124+
font-weight: 600;
125+
background-color: var(--pst-color-surface, rgba(0, 0, 0, 0.06));
126+
color: var(--pst-color-primary, #0099cd);
127+
}
128+
129+
/* Slightly smaller text for deeper levels */
130+
#salt-globaltoc .toctree-l2 > a,
131+
#salt-globaltoc .toctree-l3 > a,
132+
#salt-globaltoc .toctree-l4 > a {
133+
font-size: 0.85rem;
134+
padding: 0.18rem 0.5rem;
135+
}
136+
</style>
137+
138+
<script>
139+
(function () {
140+
function hasCurrentDescendant(ul) {
141+
return !!ul.querySelector("li.current");
142+
}
143+
144+
function initCollapsible() {
145+
var container = document.getElementById("salt-globaltoc");
146+
if (!container) return;
147+
148+
// Caption-level collapsing (start expanded by default)
149+
container.querySelectorAll("p.caption").forEach(function (caption) {
150+
var ul = caption.nextElementSibling;
151+
if (!ul || ul.tagName !== "UL") return;
152+
153+
caption.classList.add("expanded");
154+
155+
caption.addEventListener("click", function () {
156+
var isCollapsed = ul.style.display === "none";
157+
ul.style.display = isCollapsed ? "" : "none";
158+
caption.classList.toggle("expanded", isCollapsed);
159+
});
160+
});
161+
162+
// Nested li collapsing: any li that directly contains a ul
163+
container.querySelectorAll("li").forEach(function (li) {
164+
var childUl = li.querySelector(":scope > ul");
165+
if (!childUl) return;
166+
167+
var link = li.querySelector(":scope > a");
168+
if (!link) return;
169+
170+
// Distinguish sub-pages (no # in href) from intra-page section headers (# in href)
171+
var childLinks = childUl.querySelectorAll(":scope > li > a");
172+
var hasSubPages = false;
173+
childLinks.forEach(function (childLink) {
174+
var href = childLink.getAttribute("href") || "";
175+
if (href.indexOf("#") === -1) {
176+
hasSubPages = true;
177+
}
178+
});
179+
180+
if (!hasSubPages) {
181+
// Children are section headers of this page — hide them entirely
182+
childUl.style.display = "none";
183+
return;
184+
}
185+
186+
li.classList.add("has-children");
187+
188+
var btn = document.createElement("button");
189+
btn.className = "toc-toggle";
190+
btn.setAttribute("aria-label", "Toggle section");
191+
192+
var expanded = li.classList.contains("current") || hasCurrentDescendant(childUl);
193+
if (expanded) {
194+
btn.classList.add("expanded");
195+
} else {
196+
childUl.style.display = "none";
197+
}
198+
199+
link.insertAdjacentElement("afterend", btn);
200+
201+
btn.addEventListener("click", function (e) {
202+
e.stopPropagation();
203+
var isCollapsed = childUl.style.display === "none";
204+
childUl.style.display = isCollapsed ? "" : "none";
205+
btn.classList.toggle("expanded", isCollapsed);
206+
});
207+
});
208+
}
209+
210+
if (document.readyState === "loading") {
211+
document.addEventListener("DOMContentLoaded", initCollapsible);
212+
} else {
213+
initCollapsible();
214+
}
215+
})();
216+
</script>

docs/_templates/header-links.html

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<style>
2+
/* Styling for our custom links in the header center */
3+
.bd-header .navbar-nav {
4+
flex-direction: row !important;
5+
align-items: center !important;
6+
}
7+
8+
.bd-header .navbar-nav li.nav-item.custom-nav-item {
9+
margin: 0 0.75rem;
10+
}
11+
12+
.bd-header .navbar-nav li.nav-item.custom-nav-item a.nav-link {
13+
color: var(--color-foreground-secondary);
14+
text-decoration: none;
15+
font-weight: 500;
16+
font-size: 0.9rem;
17+
white-space: nowrap;
18+
padding: 0.5rem 0;
19+
transition: color 0.2s;
20+
}
21+
22+
.bd-header .navbar-nav li.nav-item.custom-nav-item a.nav-link:hover {
23+
color: var(--color-foreground-primary);
24+
}
25+
26+
.bd-header .navbar-nav li.nav-item.custom-nav-item.active a.nav-link {
27+
font-weight: 700;
28+
color: var(--pst-color-primary, var(--color-brand-primary, #0099cd));
29+
}
30+
31+
/* Style the globaltoc sidebar to look like PyData theme */
32+
.sidebar-primary-item .toctree-l1 {
33+
list-style: none;
34+
margin: 0;
35+
padding: 0;
36+
}
37+
.sidebar-primary-item .toctree-l1 > a {
38+
display: block;
39+
padding: 0.5rem 1rem;
40+
color: var(--color-foreground-secondary);
41+
text-decoration: none;
42+
font-size: 0.9rem;
43+
border-radius: 4px;
44+
}
45+
.sidebar-primary-item .toctree-l1 > a:hover {
46+
background-color: var(--color-sidebar-item-background--hover);
47+
color: var(--color-foreground-primary);
48+
}
49+
.sidebar-primary-item .toctree-l1.current > a {
50+
font-weight: 600;
51+
color: var(--color-brand-primary);
52+
}
53+
.sidebar-primary-item ul {
54+
padding-left: 1.5rem;
55+
list-style: none;
56+
}
57+
</style>
58+
59+
<!-- This template injects cross-doc navigation links into the navbar center -->
60+
<script>
61+
document.addEventListener("DOMContentLoaded", function() {
62+
const navbarCenter = document.querySelector(".navbar-header-items__center .navbar-nav");
63+
if (navbarCenter) {
64+
// Clear any existing (internal) links we don't want
65+
navbarCenter.innerHTML = "";
66+
67+
const links = [
68+
{ name: "Install Guide", url: "https://docs.saltproject.io/salt/install-guide/en/latest/", active: true },
69+
{ name: "User Guide", url: "https://docs.saltproject.io/salt/user-guide/en/latest/" },
70+
{ name: "Reference Docs", url: "https://docs.saltproject.io/en/latest/contents.html" }
71+
];
72+
73+
links.forEach(link => {
74+
const li = document.createElement("li");
75+
li.className = "nav-item custom-nav-item" + (link.active ? " active" : "");
76+
li.innerHTML = `<a class="nav-link" href="${link.url}">${link.name}</a>`;
77+
navbarCenter.appendChild(li);
78+
});
79+
}
80+
});
81+
</script>

docs/conf.py

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ def setup(app):
118118
source_suffix = {".rst": "restructuredtext"}
119119

120120
# Add any paths that contain templates here, relative to this directory.
121-
# templates_path = ["_templates"]
121+
templates_path = ["_templates"]
122122

123123
# List of patterns, relative to source directory, that match files and
124124
# directories to ignore when looking for source files.
@@ -138,11 +138,9 @@ def setup(app):
138138
# a list of builtin themes.
139139
#
140140

141-
# Base Furo Theme requirements
142-
# More: https://pradyunsg.me/furo/customisation/
143141
html_show_sourcelink = True # False on private repos; True on public repos
144-
html_theme = "furo"
145-
html_title = "Salt install guide"
142+
html_theme = "pydata_sphinx_theme"
143+
html_title = project
146144
html_baseurl = "https://docs.saltproject.io/salt/install-guide/"
147145

148146
# Extends baseurl, in combination with version value
@@ -153,14 +151,20 @@ def setup(app):
153151
"genindex.html",
154152
]
155153

154+
html_logo = None
156155
html_theme_options = {
157-
"dark_css_variables": {
158-
"color-brand-primary": "#66CCF4",
159-
"color-brand-content": "#66CCF4",
156+
"logo": {
157+
"image_light": "_static/img/SaltProject_altlogo_blue.png",
158+
"image_dark": "_static/img/SaltProject_altlogo_blue.png",
160159
},
161-
# "announcement": '<font color="orange"><strong>IMPORTANT ANNOUNCEMENT:</strong> repo.saltproject.io has migrated to packages.broadcom.com!<br /><strong><a href="https://saltproject.io/blog/post-migration-salt-project-faqs/" target="_blank" rel="noopener noreferrer">Click here for migration FAQs (2024-11-22)</a></strong></font>',
160+
"navbar_start": ["navbar-logo"],
161+
"navbar_center": ["navbar-nav", "header-links"],
162+
"navbar_end": ["theme-switcher", "navbar-icon-links"],
163+
"navigation_depth": 4,
162164
}
163165

166+
html_sidebars = {"**": ["globaltoc.html"]}
167+
164168
# Add any paths that contain custom static files (such as style sheets) here,
165169
# relative to this directory. They are copied after the builtin static files,
166170
# so a file named "default.css" will overwrite the builtin "default.css".
@@ -176,16 +180,6 @@ def setup(app):
176180

177181
copybutton_selector = "div:not(.no-copybutton) > div.highlight > pre"
178182

179-
# The name of an image file (relative to this directory) to place at the top
180-
# of the sidebar.
181-
# For example, official Salt Project docs use images from the salt-branding-guide
182-
# https://gitlab.com/saltstack/open/salt-branding-guide/
183-
#
184-
# Example for >=4.0.0 of Sphinx (support for favicon via URL)
185-
# html_logo = "https://gitlab.com/saltstack/open/salt-branding-guide/-/raw/master/logos/SaltProject_altlogo_teal.png"
186-
# Example for <4.0.0 of Sphinx, if added into _static/img/ and html_static_path is valid
187-
html_logo = "_static/img/SaltProject_altlogo_blue.png"
188-
189183
# The name of an image file (within the static path) to use as favicon of the
190184
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
191185
# pixels large. Favicons can be up to at least 228x228. PNG

docs/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
furo>=2024.8.6
1+
pydata-sphinx-theme>=0.18.0
22
sphinx-copybutton>=0.5.2
33
sphinx-design>=0.5.0
44
sphinx-inline-tabs>=2023.4.21

0 commit comments

Comments
 (0)