Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
7ea985a
feat: navigation bar dropdown
Revathyvenugopal162 May 23, 2025
f7b238c
feat: navigation bars
Revathyvenugopal162 Jun 5, 2025
9e42940
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jun 5, 2025
f39c7eb
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jun 9, 2025
0b72e44
chore: adding changelog file 723.added.md [dependabot-skip]
pyansys-ci-bot Jun 9, 2025
185f9a0
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jun 13, 2025
2e32424
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jun 16, 2025
2870ba6
Merge branch 'feat/navbar-dropdown' of https://github.com/ansys/ansys…
Revathyvenugopal162 Jun 16, 2025
b973378
feat: add styles
Revathyvenugopal162 Jun 18, 2025
09bfde1
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jun 18, 2025
9fc57a6
fix: extends the navbar
Revathyvenugopal162 Jun 18, 2025
e03a803
fix: smoketest
Revathyvenugopal162 Jun 18, 2025
2b9ff09
test: actions v9
Revathyvenugopal162 Jun 18, 2025
52cb5cc
fix: cleanup code
Revathyvenugopal162 Jun 19, 2025
7cb9ba5
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jun 23, 2025
00310a5
feat: update the docs
Revathyvenugopal162 Jun 26, 2025
2e7156a
Apply suggestions from code review
Revathyvenugopal162 Jun 26, 2025
6332c3c
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jun 27, 2025
eca07c3
fix: revert the hooks changes
Revathyvenugopal162 Jun 27, 2025
adf8061
Apply suggestions from code review
Revathyvenugopal162 Jun 27, 2025
94589a4
fix: code style
Revathyvenugopal162 Jun 27, 2025
867066e
Apply suggestions from code review
Revathyvenugopal162 Jun 27, 2025
4edc176
fix: rename navigathion theme option
Revathyvenugopal162 Jun 27, 2025
4d75e29
Merge branch 'feat/navbar-dropdown' of https://github.com/ansys/ansys…
Revathyvenugopal162 Jun 27, 2025
f5d46b5
fix(style) update the navbar
Revathyvenugopal162 Jun 27, 2025
42ca1d7
docs(docstring): add docs for navbar
Revathyvenugopal162 Jun 27, 2025
42ef786
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jun 30, 2025
7de6d62
fix: remove the unused code
Revathyvenugopal162 Jun 30, 2025
289f9e6
Merge branch 'feat/navbar-dropdown' of https://github.com/ansys/ansys…
Revathyvenugopal162 Jun 30, 2025
e041fc1
Update src/ansys_sphinx_theme/navbar_dropdown.py
Revathyvenugopal162 Jun 30, 2025
ef2c00f
fix(style): precommit
Revathyvenugopal162 Jun 30, 2025
8cd2a61
fix(log): add logging
Revathyvenugopal162 Jun 30, 2025
08e60c1
fix: update the navbar
Revathyvenugopal162 Jun 30, 2025
fe14d05
fix: update dependency
Revathyvenugopal162 Jun 30, 2025
1ff44b9
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jul 1, 2025
d9b2de7
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jul 1, 2025
0fc4866
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jul 8, 2025
fb2e52c
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jul 8, 2025
1463639
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jul 11, 2025
c1ae897
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jul 17, 2025
0847f53
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jul 18, 2025
c19a519
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jul 21, 2025
dd3dcf6
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jul 23, 2025
0e44026
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jul 24, 2025
ad0e6d9
Merge branch 'main' into feat/navbar-dropdown
Revathyvenugopal162 Jul 29, 2025
5aa40c8
Update .github/workflows/ci_cd.yml
Revathyvenugopal162 Jul 29, 2025
c8dca6e
navigation bar drop cleanup
Revathyvenugopal162 Jul 30, 2025
1c1be16
Apply suggestions from code review
Revathyvenugopal162 Jul 30, 2025
c3b0d6c
fix: update the rst
Revathyvenugopal162 Jul 30, 2025
ed02c16
fix: pre-commit
Revathyvenugopal162 Jul 30, 2025
3015295
Update src/ansys_sphinx_theme/navbar_dropdown.py
Revathyvenugopal162 Jul 30, 2025
46d2595
fix: simplyfy
Revathyvenugopal162 Jul 30, 2025
c2efeca
fix: simplyfy
Revathyvenugopal162 Jul 30, 2025
38196dc
fix: code style
Revathyvenugopal162 Jul 30, 2025
b3b5e27
fix: cleanup
Revathyvenugopal162 Jul 30, 2025
a2a084f
fix: precommit fix
Revathyvenugopal162 Jul 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/723.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Dropdown for navigation bar
48 changes: 47 additions & 1 deletion doc/source/user-guide/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@

print("hello world")

If a format is used in the "content" field that does not fall into the categories above, it will not

Check warning on line 347 in doc/source/user-guide/options.rst

View workflow job for this annotation

GitHub Actions / vale

[vale] doc/source/user-guide/options.rst#L347

[Google.Will] Avoid using 'will'.
Raw output
{"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "doc/source/user-guide/options.rst", "range": {"start": {"line": 347, "column": 93}}}, "severity": "WARNING"}
be rendered correctly.

To enable the "What's new" sections and sidebar in the changelog file, add the following dictionary
Expand All @@ -367,9 +367,9 @@
The dictionary contains the following keys:

- ``whatsnew_file_path``: The path to the YAML file containing what's new content local to the
``doc/source`` directory. If not provided, the what's new section will not be generated.

Check warning on line 370 in doc/source/user-guide/options.rst

View workflow job for this annotation

GitHub Actions / vale

[vale] doc/source/user-guide/options.rst#L370

[Google.Will] Avoid using 'will'.
Raw output
{"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "doc/source/user-guide/options.rst", "range": {"start": {"line": 370, "column": 69}}}, "severity": "WARNING"}
- ``changelog_file_path``: The path to the changelog.rst file local to the ``doc/source``
directory. If not provided, the what's new section will not be generated.

Check warning on line 372 in doc/source/user-guide/options.rst

View workflow job for this annotation

GitHub Actions / vale

[vale] doc/source/user-guide/options.rst#L372

[Google.Will] Avoid using 'will'.
Raw output
{"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "doc/source/user-guide/options.rst", "range": {"start": {"line": 372, "column": 54}}}, "severity": "WARNING"}
- ``sidebar_pages``: List of names for the pages to include the what's new sidebar on. If not
provided, the what's new sidebar is not displayed.
- ``sidebar_no_of_headers``: Number of minor version sections to display in the what's new sidebar.
Expand All @@ -394,6 +394,52 @@

.. note::

If you are using both the "whatsnew" and "cheatsheet" options, the "cheatsheet" option will be

Check warning on line 397 in doc/source/user-guide/options.rst

View workflow job for this annotation

GitHub Actions / vale

[vale] doc/source/user-guide/options.rst#L397

[Google.Will] Avoid using 'will'.
Raw output
{"message": "[Google.Will] Avoid using 'will'.", "location": {"path": "doc/source/user-guide/options.rst", "range": {"start": {"line": 397, "column": 92}}}, "severity": "WARNING"}
displayed first in the left navigation pane, followed by the "What's new" section to maintain
sidebar consistency.
sidebar consistency.

Navigation bar dropdown
------------------------
This theme supports dropdown navigation bars. The layout is declared using a YAML file contained at any level in the ``doc/source`` directory.
The file path is relative to the ``doc/source`` directory,and must be specified in the ``html_theme_options`` dictionary.

- ``navigation_yaml_file``: The path to the YAML file containing the navigation structure.

.. code:: python

html_theme_options = {
...,
"navigation_dropdown": {
"layout_file": "navbar.yml", # Relative path to the YAML file
},
}

Each entry in the YAML file may include the following fields:

- **file.** The relative path to the documentation file, based on the doc/source directory.

- **title.** The text displayed for the link in the dropdown navigation menu.

- **sections.** A list of nested navigation items. Each section can specify its own file, title, and an optional caption to provide a brief description.

.. code:: yaml

- file: api/index
title: "API Reference"

- file: examples
title: "Examples"
sections:

- file: examples/sphinx-design.rst
title: "Sphinx Design Examples"
caption: Examples of using Sphinx design features

- file: examples/nbsphinx
title: "Nbsphinx Examples"
caption: Examples of using Nbsphinx for Jupyter Notebooks


.. warning::

You must declare the complete layout of the dropdown navigation bar in the YAML file. Sphinx does not resolve it automatically.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies = [
"Jinja2>=3.1.2",
"importlib-metadata>=4.0",
"pdf2image>=1.17.0",
"PyYAML==6.0.2",
]

[project.optional-dependencies]
Expand Down
4 changes: 4 additions & 0 deletions src/ansys_sphinx_theme/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from ansys_sphinx_theme.cheatsheet import build_quarto_cheatsheet, cheatsheet_sidebar_pages
from ansys_sphinx_theme.extension.linkcode import DOMAIN_KEYS, sphinx_linkcode_resolve
from ansys_sphinx_theme.latex import generate_404
from ansys_sphinx_theme.navbar_dropdown import load_navbar_configuration, update_template_context
from ansys_sphinx_theme.search import (
create_search_index,
update_search_config,
Expand Down Expand Up @@ -494,6 +495,7 @@ def setup(app: Sphinx) -> Dict:

# Add default HTML configuration
setup_default_html_theme_options(app)
load_navbar_configuration(app)

# Check for what's new options in the theme configuration
whatsnew_file, changelog_file = get_whatsnew_options(app)
Expand All @@ -519,10 +521,12 @@ def setup(app: Sphinx) -> Dict:
app.connect("html-page-context", update_footer_theme)
app.connect("html-page-context", fix_edit_html_page_context)
app.connect("html-page-context", update_search_sidebar_context)
app.connect("html-page-context", update_template_context)

app.connect("build-finished", replace_html_tag)
if use_ansys_search:
app.connect("build-finished", create_search_index)

return {
"version": __version__,
"parallel_read_safe": True,
Expand Down
155 changes: 155 additions & 0 deletions src/ansys_sphinx_theme/navbar_dropdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Copyright (C) 2021 - 2025 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: MIT
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

"""Navigation Dropdown for navigation bar."""

import copy
from functools import lru_cache
import pathlib
from typing import TypedDict

import bs4
from docutils import nodes
import sphinx
from sphinx.util import logging
from sphinx.util.nodes import make_refnode
import yaml

logger = logging.getLogger(__name__)


class NavEntry(TypedDict, total=False):
"""Represents an entry in the navbar configuration."""

title: str
caption: str
file: str
link: str
sections: list["NavEntry"]


def load_navbar_configuration(app: sphinx.application.Sphinx) -> None:
"""Load the navbar configuration from a YAML file for the Sphinx app."""
navigation_theme_options = app.config.html_theme_options.get("navigation_dropdown", {})
if not navigation_theme_options or "layout_file" not in navigation_theme_options:
return

layout_file = navigation_theme_options["layout_file"]

yaml_path = pathlib.Path(app.srcdir) / layout_file

try:
yaml_content = yaml_path.read_text(encoding="utf-8")
app.config.navbar_contents = yaml.safe_load(yaml_content)

except FileNotFoundError:
raise FileNotFoundError(
f"Navbar layout file '{layout_file}' not found in: {yaml_path.parent.resolve()}"
)

except yaml.YAMLError as exc:
raise ValueError(f"Failed to parse YAML in '{yaml_path.name}': {exc}")


def update_template_context(
app: sphinx, pagename: str, templatename: str, context: dict, doctree: nodes.document | None
) -> None:
"""Inject navbar rendering logic into the Sphinx HTML template context."""

@lru_cache(maxsize=None)
def render_navbar_links_html() -> bs4.BeautifulSoup:
"""Render the navbar content as HTML using the navbar configuration."""
if not hasattr(app.config, "navbar_contents"):
raise ValueError("Navbar configuration not found. Please define a layout YAML file.")

nav_root = nodes.container(classes=["navbar-content"])
nav_root.append(build_navbar_nodes(app.config.navbar_contents))
rendered = app.builder.render_partial(nav_root)["fragment"]
return add_navbar_chevrons(bs4.BeautifulSoup(rendered, "html.parser"))

def build_navbar_nodes(entries: list[NavEntry], is_top_level: bool = True) -> nodes.bullet_list:
"""Recursively construct docutils nodes for the navbar structure."""
classes = ["navbar-toplevel"] if is_top_level else ["navbar-sublevel"]
nav_list = nodes.bullet_list(bullet="-", classes=classes)

for entry in entries:
title = entry.get("title", "")
file = entry.get("file")
link = entry.get("link")

if file:
ref_node = make_refnode(
app.builder,
context["current_page_name"],
file,
None,
nodes.inline(classes=["navbar-link-title"], text=title),
title,
)
elif link:
ref_node = nodes.reference("", "", internal=False)
ref_node["refuri"] = link
ref_node["reftitle"] = title
ref_node.append(nodes.inline(classes=["navbar-link-title"], text=title))
else:
logger.warning(
f"Invalid navbar entry: {entry}. Expected 'file' or 'link'. Skipping."
)
continue

if "caption" in entry:
ref_node.append(nodes.Text(entry["caption"]))

paragraph = nodes.paragraph()
paragraph.append(ref_node)

container = nodes.container(classes=["ref-container"])
container.append(paragraph)

list_item_classes = ["active-link"] if file == pagename else []
list_item = nodes.list_item(classes=list_item_classes)
list_item.append(container)

if "sections" in entry:
dropdown = nodes.container(classes=["navbar-dropdown"])
dropdown.append(build_navbar_nodes(entry["sections"], is_top_level=False))
list_item.append(dropdown)

nav_list.append(list_item)

return nav_list

context["render_navbar_links_html"] = render_navbar_links_html


def add_navbar_chevrons(soup: bs4.BeautifulSoup) -> bs4.BeautifulSoup:
"""Add chevron icons to navbar items that have dropdown menus."""
soup_copy = copy.copy(soup)

for li in soup_copy.find_all("li", recursive=True):
if li.find("div", class_="navbar-dropdown", recursive=False):
ref_container = li.find("div", class_="ref-container")
if ref_container:
chevron = soup_copy.new_tag("i", attrs={"class": "fa-solid fa-chevron-down"})
ref_container.append(chevron)

return soup_copy
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
{% if theme_navigation_dropdown %}
<nav class="navbar-nav">
<p class="sidebar-header-items__title"
role="heading"
aria-level="1"
aria-label="{{ _('Site Navigation') }}">
<!-- {{ _("Site Navigation") }} -->
</p>
{{ render_navbar_links_html() }}
</nav>
<style>
/* Top navbar styling */
.navbar-toplevel p {
margin: 0;
padding-inline-start: 0;
}
div.navbar-dropdown {
display: none;
position: relative;
left: -100%;
margin-top: 2rem;
}
.navbar-sublevel p a.reference {
text-decoration: none;
}
.navbar-sublevel p a.reference:hover > span.navbar-link-title {
text-decoration: underline;
color: var(--pst-color-link-hover);
}
.navbar-toplevel li {
display: inline-flex;
justify-content: center;
align-items: center;
height: 100%;
padding: 0em 1em;

}

ul.navbar-toplevel li:hover > div.navbar-dropdown {
display: block;
}

.navbar-content ul.navbar-sublevel {
position: absolute;
gap: 1rem;
padding: 1em 1em;
display: flex;
flex-direction: column;
align-items: baseline;
background-color: white;
white-space: pre;
box-shadow: 0 5px 15px 0 rgb(0 0 0 / 10%);
}

div.navbar-content a {
display: flex;
flex-direction: column;
color: var(--ast-navbar-color);
align-items: baseline;
justify-content: center;
font-weight: var(--ast-font-weight-semibold);
}
.ref-container {
display: flex;
flex-direction: row;
gap: 0.5em;
font-family: var(--ast-body-family);
line-height: var(--ast-global-line-height);
align-items: center;
justify-content: center;
height: 100%;
}
/* Highlight active nav bar link */
li.active-link {
font-weight: bold;
}
/* Set the first .navbar-persistent--mobile element to have auto left margin */
/* Disable underline for hovered links in the nav bar */
.navbar-nav li a:hover {
text-decoration: none;
}
.navbar-header-items {
padding-left: 0;
}
</style>
{% else %}
{% include "pydata_sphinx_theme/components/navbar-nav.html" %}
{% endif %}
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ whatsnew =
use_ansys_search = True
search_extra_sources =
search_filters =
navigation_dropdown =
Loading