Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from crate.theme.rtd.conf.theme import *

# Note: TOC toggle icons are enabled by default in the theme.
# To disable: html_theme_options["toc_toggle_icons"] = "false"

# Mimic some bits of the RTD context being propagated to its Sphinx builder.
# https://github.com/readthedocs/readthedocs.org/blob/main/readthedocs/doc_builder/backends/sphinx.py
html_context.update({
Expand Down
4 changes: 4 additions & 0 deletions src/crate/theme/rtd/crate/sidebar.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
{% if theme_toc_toggle_icons == 'true' %}
<div role="complementary" class="bs-docs-sidebar hidden-print toc-toggle-icons-enabled">
{% else %}
<div role="complementary" class="bs-docs-sidebar hidden-print">
{% endif %}
{% if project == 'SQL 99' %}
<div class="search-link">
{% if pagename == "search" %}
Expand Down
67 changes: 67 additions & 0 deletions src/crate/theme/rtd/crate/static/css/crateio-rtd.css
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,70 @@ a code {
div.topic {
border: 0;
}

/* ========================================================================== */
/* TOC Toggle Icons for Navigation */
/* ========================================================================== */
/* Enable by adding class "toc-toggle-icons-enabled" to sidebar container */
/* Configuration: Set html_theme_options['toc_toggle_icons'] = true */
/* ========================================================================== */

/* Add expand/collapse icon to items that have children */
/* Modern browsers: Use :has() selector */
.toc-toggle-icons-enabled .bs-docs-sidenav .toctree li:has(> ul) > a::after,
/* Fallback: Use .has-children class added by JavaScript */
.toc-toggle-icons-enabled .bs-docs-sidenav .toctree li.has-children > a::after {
content: "";
display: inline-block;
width: 16px;
height: 16px;
margin-left: auto;
padding-left: 8px;
background-color: var(--color-sidebar-left-link);
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M15.707 12l-5.647-5.646a.5.5 0 01.708-.708l6 6a.5.5 0 010 .708l-6 6a.5.5 0 01-.708-.708L15.707 12z"/></svg>');
mask-size: contain;
mask-repeat: no-repeat;
mask-position: center;
transform: rotate(0deg);
/* transition: transform 0.3s ease-in-out, opacity 0.2s ease; */
/* Animation disabled - pages reload on navigation, causing animation to play on every page load */
opacity: 0.6;
flex-shrink: 0;
}

/* Rotate icon when parent is expanded (current/active or manually expanded) */
/* Modern browsers: Use :has() selector */
.toc-toggle-icons-enabled .bs-docs-sidenav .toctree li.expanded:has(> ul) > a::after,
.toc-toggle-icons-enabled .bs-docs-sidenav .toctree li.has-children.expanded > a::after {
transform: rotate(90deg);
}

/* Hover effect on icon */
.toc-toggle-icons-enabled .bs-docs-sidenav .toctree li:has(> ul) > a:hover::after,
.toc-toggle-icons-enabled .bs-docs-sidenav .toctree li.has-children > a:hover::after {
opacity: 1;
}

/* Make link a flex container to position icon at the right */
.toc-toggle-icons-enabled .bs-docs-sidenav .toctree li:has(> ul) > a,
.toc-toggle-icons-enabled .bs-docs-sidenav .toctree li.has-children > a {
display: flex;
align-items: center;
}

/* Make cursor indicate clickable area for items with children */
.toc-toggle-icons-enabled .bs-docs-sidenav .toctree li:has(> ul) > a,
.toc-toggle-icons-enabled .bs-docs-sidenav .toctree li.has-children > a {
cursor: pointer;
}

/* JavaScript-driven expand/collapse behavior */
/* Hide all children by default when JS is enabled */
.toc-toggle-icons-enabled .bs-docs-sidenav .toctree li.has-children > ul {
display: none !important;
}

/* Show children when parent is marked as expanded */
.toc-toggle-icons-enabled .bs-docs-sidenav .toctree li.has-children.expanded > ul {
display: block !important;
}
1 change: 1 addition & 0 deletions src/crate/theme/rtd/crate/static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ import "sticky-sidebar";
import "./util";
import "./crate";
import "./custom";
import "./toc-toggle";
99 changes: 99 additions & 0 deletions src/crate/theme/rtd/crate/static/js/toc-toggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/**
* TOC Navigation Expand/Collapse Toggle
*
* This module adds interactive expand/collapse functionality to the table of
* contents (TOC) navigation sidebar. It allows users to click on parent items
* to show/hide their children.
*
* Features:
* - Click on items with children to toggle expand/collapse
* - Auto-expand current page's parent hierarchy
* - Add .has-children class for browsers without :has() support
* - ARIA attributes for accessibility
*/
Comment on lines +1 to +13
Copy link
Member

@amotl amotl Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi. Is it using the implementation from Furo like planned?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at all.
I don't see anything mentioned in that PR about the icons? Perhaps I'm missing something and we can get this in a different way? Possibly even more advanced or nicer looking?

Copy link
Member

@amotl amotl Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have clarified better: While the patch referenced above is about bringing in alternative navigation definition mechanics, it demonstrates that all the CSS/JS elements to support Furo's navigation runtime mechanics are already included into the assets bundle, so I was wondering if we can use those instead of bringing in a separate implementation about the same topic.

Copy link
Member

@amotl amotl Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me be more explicit by referring to relevant components of Furo defined in assets/styles/components/_sidebar.sass:

[...] use those instead of bringing in a separate implementation [...]
Possibly even more advanced or nicer looking?

I didn't dive into finding out how the currently defined navigation can be connected to those Furo mechanics, also because my patch was exploring another direction. If you can make sense how to connect the HTML in sidebartoc.html with Furo's JS/CSS, that could possibly bring both worlds together, with the benefit of perfect rendering and collapsing mechanics by battle-tested Furo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the CSS/JS elements to support Furo's navigation runtime mechanics are already included

Furo's assets/styles/components/_sidebar.sass has been vendorized into our theme already, apparently from Furo 2024.05.06.

https://github.com/crate/crate-docs-theme/blob/main/src/crate/theme/rtd/crate/static/vendor/furo/styles/components/_sidebar.sass

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pointers!
I wonder if these are the attractive benefits we could gain:

  1. Better tested
  2. collapsing/uncollapsing sections independently of others (so uncollapsing don't close other uncollapsed sections - as today)
  3. visuals better

The main (80%) value is already achieved through this PR, but I can take a look.
I'm not sure what the effort would be, but if it's not too much I can do it. Otherwise we could take this in as a first step, and improve as a next step.

Copy link
Member

@amotl amotl Dec 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent, thanks. Please proceed at your disposal.


/**
* Initialize TOC toggle behavior
*/
function initTocToggle() {
// Only initialize if the toc-toggle-icons-enabled class is present
const sidebar = document.querySelector('.toc-toggle-icons-enabled .bs-docs-sidenav');
if (!sidebar) {
return;
}

const tocItems = sidebar.querySelectorAll('.toctree li');

tocItems.forEach(item => {
// Check if item has direct ul children (already rendered)
const hasDirectChildren = item.querySelector(':scope > ul');

// Check if the link suggests this is a parent page (links to index.html or has children)
const link = item.querySelector(':scope > a');
const linkHref = link ? link.getAttribute('href') : '';
const linksToIndex = linkHref && (linkHref.endsWith('/index.html') || linkHref.includes('/index.html#'));

// Check if this item is alone in its parent <ul> (no siblings)
// Items with children are typically alone in their own <ul> block
const parentUl = item.parentElement;
const siblings = parentUl ? parentUl.querySelectorAll(':scope > li') : [];
const isAloneInUl = siblings.length === 1;

// Determine if item has or could have children:
// - Has direct children already rendered, OR
// - Links to index.html AND is alone in its <ul> (likely a section with children)
const hasChildren = hasDirectChildren || (linksToIndex && isAloneInUl);
if (!hasChildren) return;

// Add .has-children class for browsers without :has() support
item.classList.add('has-children');

// Link was already found above, skip if not found
if (!link) return;

// Mark as expanded if in current path
const isCurrentPath = item.classList.contains('current') ||
item.classList.contains('active');
if (isCurrentPath) {
item.classList.add('expanded');
}

link.setAttribute('aria-expanded', item.classList.contains('expanded').toString());

// Add click handler to toggle expansion (only if item has direct children to toggle)
if (hasDirectChildren) {
link.addEventListener('click', (e) => {
// Check if clicking on the icon area (right side)
const linkRect = link.getBoundingClientRect();
const clickX = e.clientX - linkRect.left;
const linkWidth = linkRect.width;

// If clicking in the last 30px (icon area), toggle instead of navigate
if (clickX > linkWidth - 30) {
e.preventDefault();
toggleItem(item, link);
}
});
}
// For items that link to index.html but don't have children rendered yet,
// clicking the icon will navigate to the page (normal link behavior)
});
}

/**
* Toggle expand/collapse state of a TOC item
* @param {HTMLElement} item - The list item element
* @param {HTMLElement} link - The anchor element
*/
function toggleItem(item, link) {
const isExpanded = item.classList.toggle('expanded');
link.setAttribute('aria-expanded', isExpanded.toString());
}

// Run after DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTocToggle);
} else {
// DOM already loaded
initTocToggle();
}
4 changes: 4 additions & 0 deletions src/crate/theme/rtd/crate/theme.conf
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ tracking_hubspot_id =
# Can be used the query string of a resource URL for HTTP cache busting
ver =

# TOC toggle icons - show expand/collapse icons in sidebar navigation
# Values: "true" (default) or "false"
toc_toggle_icons = true

# Furo
announcement =
dark_css_variables =
Expand Down