Skip to content

Commit

Permalink
ENH: Left navigation menu to collapse at the "part" level (#594)
Browse files Browse the repository at this point in the history
* restructuring html for parts

* added styling and restructured bs html

* adding list-caption class

* not adding collapse parts for now

* modified tests

* pre-commit

* Update src/pydata_sphinx_theme/__init__.py

Co-authored-by: Chris Holdgraf <choldgraf@gmail.com>

* opening captions explicitly

* making parts text clickable

* style for part label

* pre-commit

* corrections in __init__ and tests

* adding docs for show_nav_levek: 0

* parts collapse only when show_nav_level: 0

* tests update

* added test for sidebar nav_level0

* docs edit

* parts chevron style

* Update docs/user_guide/configuring.rst

Co-authored-by: Chris Holdgraf <choldgraf@gmail.com>

* Update src/pydata_sphinx_theme/__init__.py

Co-authored-by: Chris Holdgraf <choldgraf@gmail.com>

* Update src/pydata_sphinx_theme/__init__.py

Co-authored-by: Chris Holdgraf <choldgraf@gmail.com>

* Update src/pydata_sphinx_theme/__init__.py

Co-authored-by: Chris Holdgraf <choldgraf@gmail.com>

* Update src/pydata_sphinx_theme/__init__.py

Co-authored-by: Chris Holdgraf <choldgraf@gmail.com>

* Update src/pydata_sphinx_theme/__init__.py

Co-authored-by: Chris Holdgraf <choldgraf@gmail.com>

* tests and some code refactoring

* Clarify this in the docs

* Update docs/user_guide/configuring.rst

* Cleaning up docs

Co-authored-by: Chris Holdgraf <choldgraf@gmail.com>
Co-authored-by: Chris Holdgraf <choldgraf@berkeley.edu>
  • Loading branch information
3 people committed Mar 11, 2022
1 parent 3c11db8 commit 6711e9b
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 37 deletions.
32 changes: 30 additions & 2 deletions docs/user_guide/configuring.rst
Expand Up @@ -245,8 +245,8 @@ override this behavior and control the sidebar on a per-page basis, use the

.. _navigation-depth:

Navigation depth and collapsing of the sidebar
==============================================
Navigation depth and collapsing the sidebar
===========================================

By default, this theme enables to expand/collapse subsections in the left
sidebar navigation (without actually navigating to the page itself), and this extends
Expand All @@ -272,6 +272,34 @@ default, you can use the following configuration in ``conf.py``:
This will make the first two navigations show up by default (AKA, top-level
pages and their immediate children).

Collapse entire toctree captions / parts
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If your ``toctree`` elements have captions assigned to them (with ``:caption:``), you may
collapse navigation items so that only the caption is visible. Clicking on the
caption will display the items below.

To enable this behavior, set the ``show_nav_level`` value to 0, like below:

.. code:: python
html_theme_options = {
"show_nav_level": 0
}
You can only collapse your ``toctree`` items underneath their caption if a caption is defined for them!
If your ``toctree`` does not have a caption defined, then all of the pages underneath it will be displayed
(the same as the default theme behavior). See `the toctree documentation <https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#directive-toctree>`_
for more details.

.. note::

In some Sphinx sites, the top-level ``toctree`` groupings make up "parts" in the documentation, with
each page beneath making up a "chapter".

.. _remove_toctrees:

Selectively remove pages from your sidebar
Expand Down
30 changes: 30 additions & 0 deletions src/pydata_sphinx_theme/__init__.py
Expand Up @@ -147,6 +147,25 @@ def generate_nav_html(kind, startdepth=None, show_nav_level=1, **kwargs):
for ul in soup("ul", recursive=False):
ul.attrs["class"] = ul.attrs.get("class", []) + ["nav", "bd-sidenav"]

# Add collapse boxes for parts/captions.
# Wraps the TOC part in an extra <ul> to behave like chapters with toggles
# show_nav_level: 0 means make parts collapsible.
if show_nav_level == 0:
partcaptions = soup.find_all("p", attrs={"class": "caption"})
if len(partcaptions):
new_soup = bs("<ul class='list-caption'></ul>", "html.parser")
for caption in partcaptions:
# Assume that the next <ul> element is the TOC list
# for this part
for sibling in caption.next_siblings:
if sibling.name == "ul":
toclist = sibling
break
li = soup.new_tag("li", attrs={"class": "toctree-l0"})
li.extend([caption, toclist])
new_soup.ul.append(li)
soup = new_soup

# Add icons and labels for collapsible nested sections
_add_collapse_checkboxes(soup)

Expand Down Expand Up @@ -280,6 +299,12 @@ def _add_collapse_checkboxes(soup):
# We check all "li" elements, to add a "current-page" to the correct li.
classes = element.get("class", [])

# expanding the parent part explicitly, if present
if "current" in classes:
parentli = element.find_parent("li", class_="toctree-l0")
if parentli:
parentli.select("p.caption ~ input")[0].attrs["checked"] = ""

# Nothing more to do, unless this has "children"
if not element.find("ul"):
continue
Expand All @@ -294,8 +319,12 @@ def _add_collapse_checkboxes(soup):
# Add the "label" for the checkbox which will get filled.
if soup.new_tag is None:
continue

label = soup.new_tag("label", attrs={"for": checkbox_name})
label.append(soup.new_tag("i", attrs={"class": "fas fa-chevron-down"}))
if "toctree-l0" in classes:
# making label cover the whole caption text with css
label["class"] = "label-parts"
element.insert(1, label)

# Add the checkbox that's used to store expanded/collapsed state.
Expand All @@ -308,6 +337,7 @@ def _add_collapse_checkboxes(soup):
"name": checkbox_name,
},
)

# if this has a "current" class, be expanded by default
# (by checking the checkbox)
if "current" in classes:
Expand Down
84 changes: 49 additions & 35 deletions src/pydata_sphinx_theme/assets/styles/index.scss
Expand Up @@ -165,34 +165,6 @@ footer {
}
}

.bd-sidebar {
padding-top: 1em;
overflow-y: auto;
display: flex;
flex-direction: column;

@include media-breakpoint-up(md) {
border-right: 1px solid rgba(0, 0, 0, 0.1);

@supports (position: -webkit-sticky) or (position: sticky) {
position: -webkit-sticky;
position: sticky;
top: var(--pst-header-height);
z-index: 1000;
height: calc(100vh - var(--pst-header-height));
}
}

&.no-sidebar {
border-right: 0;
}

.sidebar-end-items {
margin-top: auto;
margin-bottom: 1em;
}
}

.bd-links {
padding-top: 1rem;
padding-bottom: 1rem;
Expand Down Expand Up @@ -346,7 +318,8 @@ nav.bd-links {

/* Nav: hide second level (shown on .active) */

.bd-toc .nav {
.bd-toc .nav,
.list-caption {
.nav {
display: none;

Expand Down Expand Up @@ -522,8 +495,44 @@ nav.bd-links {
}

.bd-sidebar {
padding-top: 1em;
overflow-y: auto;
display: flex;
flex-direction: column;

@include media-breakpoint-up(md) {
border-right: 1px solid rgba(0, 0, 0, 0.1);

@supports (position: -webkit-sticky) or (position: sticky) {
position: -webkit-sticky;
position: sticky;
top: var(--pst-header-height);
z-index: 1000;
height: calc(100vh - var(--pst-header-height));
}
}

&.no-sidebar {
border-right: 0;
}

.sidebar-end-items {
margin-top: auto;
margin-bottom: 1em;
}
.list-caption {
list-style: none;
padding-left: 0px;
}
li {
position: relative;
// If it has children, add a bit more padding to wrap the content to avoid
// overlapping with the <label>
&.has-children {
> .reference {
padding-right: 30px;
}
}
}
label {
position: absolute;
Expand Down Expand Up @@ -553,12 +562,17 @@ nav.bd-links {
}
}
}

// If it has children, add a bit more padding to wrap the content to avoid
// overlapping with the <label>
li.has-children {
> .reference {
padding-right: 30px;
.label-parts {
width: 100%;
height: 100%;
&:hover {
background: none;
}
i {
width: 30px;
position: absolute;
top: 0.3em; // aligning chevron with text
right: 0em; // aligning chevron to the right
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions tests/test_build.py
Expand Up @@ -281,6 +281,46 @@ def test_sidebars_level2(sphinx_build_factory, file_regression):
file_regression.check(sidebar.prettify(), extension=".html")


def test_sidebars_show_nav_level0(sphinx_build_factory, file_regression):
"""
Regression test for show_nav_level:0 when the toc is divided into parts.
Testing both home page and a subsection page for correct elements.
"""
confoverrides = {"html_theme_options.show_nav_level": 0}
sphinx_build = sphinx_build_factory("sidebars", confoverrides=confoverrides).build()

# 1. Home Page
index_html = sphinx_build.html_tree("section1/index.html")
sidebar = index_html.select("nav#bd-docs-nav")[0]

# check if top-level ul is present
ul = sidebar.find("ul")
assert "list-caption" in ul.attrs["class"]

# get all li elements
li = ul.select("li")

# part li
assert "toctree-l0 has-children" in " ".join(li[0].attrs["class"])
assert "caption-text" in li[0].select("p span")[0].attrs["class"]
assert "label-parts" in li[0].find("label").attrs["class"]

# basic checks on other levels
assert "toctree-l1 has-children" in " ".join(li[1].attrs["class"])
assert "toctree-l2" in li[2].attrs["class"]

# 2. Subsection Page
subsection_html = sphinx_build.html_tree("section1/subsection1/index.html")
sidebar = subsection_html.select("nav#bd-docs-nav")[0]

# get all input elements
input_elem = sidebar.select("input")

# all input elements should be collapsed in this page
for ii in input_elem:
assert "checked" in ii.attrs


def test_included_toc(sphinx_build_factory):
"""Test that Sphinx project containing TOC (.. toctree::) included
via .. include:: can be successfully built.
Expand Down

0 comments on commit 6711e9b

Please sign in to comment.