feat(sidebar): data-driven sidebar with section scoping, merge modes, and state persistence - #1006
Draft
imfing wants to merge 62 commits into
Draft
feat(sidebar): data-driven sidebar with section scoping, merge modes, and state persistence#1006imfing wants to merge 62 commits into
imfing wants to merge 62 commits into
Conversation
Remove all define blocks. Both auto and data paths produce identical normalized node structures consumed by a single render pipeline.
Hugo does not support return-with-value in recursive partial calls. Restructure: data tree is normalized iteratively (unrolled 3 levels), auto tree children are discovered inline during rendering.
Swap inline Tailwind utility classes for the semantic hextra-sidebar-* classes defined in a3a462e, completing the class refactor in the top-level orchestrator template.
The build-* partials return node arrays that depend only on (navRoot, language) and site.Menus.main — never on the current page. Wrap both top-level orchestrator calls with partialCached so the tree data is computed once per (lang, navRoot) instead of once per page. Per-page state (active link, open ancestor) remains handled by the render-* partials and stays uncached. Measured on docs site (122 pages, 4 languages): - build-mobile-tree: 62.96ms -> 8.03ms cumulative (-87%) - build-auto-nodes: 724 -> 33 invocations (-95%) - Total sidebar build layer: ~80ms -> ~22ms (-72%) Savings scale with page count, so larger sites benefit more.
Extract recursive auto-tree discovery into a dedicated cached partial. Previously the same logic was duplicated across the desktop, mobile, and data-tree builders, and render-tree.html fell back to inline discovery for nodes with empty children. Both build-tree and build-mobile-tree now call build-auto-nodes for their auto-discovery needs, and build-data-tree uses it to resolve children for merge: deep nodes. Caching is keyed by section RelPermalink so each subtree is computed once per build.
Replace inline Tailwind utility classes in the render-* partials with the semantic hextra-sidebar-* classes defined in a3a462e. Covers separator items, child list containers, item wrappers, TOC links, and collapsible button focus styles. Drops the conditional hx:flex hx:flex-col on nested <li> elements — the parent .hextra-sidebar-children rule now provides this layout for every depth uniformly.
Wrap sidebar.css in @layer components and add the missing container-level rules (mobile/desktop list, scroll wrapper, search wrapper, placeholder, switches, theme-toggle, guide-list, toc-link) used by the orchestrator and render partials. Regenerate compiled/main.css and docs/hugo_stats.json so the Tailwind pipeline picks up the new class names.
Hugo's default security policy blocks Node-based asset pipelines (PostCSS, Tailwind) unless allow-listed. Disable the node permission gate in dev.toml so the local dev server can run PostCSS without per-command exec policies.
Replace fragile Tailwind utility class selectors (e.g. hx\:max-md\:hidden) with the stable hextra-sidebar-desktop-list and hextra-sidebar-mobile-list semantic classes. The tests no longer break when utility classes are reshuffled in the layout.
…mobile Three regressions surfaced by review of the data-driven sidebar: 1. build-tree.html always merged data nodes into the desktop tree as soon as a data file existed, so customizations leaked into unrelated sections (e.g. a data file scoped to /docs/sidebar-lab/ prepended that node on /docs/getting-started/). Restore the $matchFound gate from main: the data tree is only active when the current page sits under one of its declared roots. The match is computed once in sidebar.html and threaded through both the desktop and mobile builders, with the boolean joined into the partialCached variant keys so cache buckets stay correct. 2. build-mobile-tree.html expanded each main-menu section with build-auto-nodes, bypassing data rules entirely. Mobile users on /docs/sidebar-lab/manual-parent/ saw the auto child even though merge:none should hide it. Mobile now reuses build-tree for section expansion so merge modes, ordering, and titles are applied consistently across viewports. 3. When site.Menus.main was empty (or filtered down to icon-only entries), the mobile tree returned an empty list, dropping all docs navigation. Re-add the auto-tree fallback by deferring to build-tree on the resolved navRoot when no usable menu entries were produced. build-tree.html also filters data roots to descendants of $context so the new mobile reuse can pass arbitrary section roots without pulling in unrelated data entries.
Switch the data-driven sidebar to Hugo's language subfolder convention (`data/<lang>/sidebar.yaml`) so it aligns with the existing termbase layout and Hugo's recommended pattern for localized data. The legacy `data/sidebar.<lang>.yaml` form is no longer recognized; `data/sidebar.yaml` remains as the language-agnostic fallback.
Add an `icon` property to the sidebar data schema so authors can render a Hextra icon (from data/icons.yaml) before a node title. The icon is wrapped together with the title in an inner flex span so the parent link's justify-between layout does not separate them.
Previously, when a page matched a data sidebar root, the data nodes were placed first and the remaining auto-generated sections were appended after, which forced data sections to the top regardless of weight. Walk the auto tree instead and substitute data versions in place by link, so data-driven sections inherit their natural weight-ordered position.
The data tree was only consulted when the current page sat under a data-defined root, so cosmetic properties such as `icon` disappeared on every other page. Compute the data tree unconditionally and, when not matched, overlay only the icon onto matching auto-nodes by link; the structural overrides (items, merge, open) remain gated on the match so data-defined navigation does not leak into unrelated sections.
Read `sidebar.icon` from page front matter when building auto-tree nodes, merge:deep auto-appended children, and the mobile menu tree. Data-driven nodes also fall back to the linked page's front-matter icon when their own `icon` is omitted, so authors can attach an icon to a page without listing it in the data file. An explicit `icon` in the data file still takes precedence.
Use the new front-matter `sidebar.icon` to give Getting Started, Guide, and Advanced distinct icons in the sidebar. Mirrored across all four language variants.
Replace the abrupt h-0/h-auto toggle with a grid-template-rows 0fr↔1fr transition so nested sections expand smoothly, matching the mobile sidebar's cubic-bezier easing. Guard the transition behind prefers-reduced-motion: no-preference and disable the chevron rotation when reduce is set.
# Conflicts: # assets/css/compiled/main.css
# Conflicts: # assets/css/compiled/main.css # assets/css/components/sidebar.css # assets/js/core/banner.js # assets/js/flexsearch.js # docs/content/docs/guide/configuration.md # docs/hugo_stats.json # layouts/_partials/sidebar.html
A section (or data node) with `sidebar.separator: true` renders as a top-level separator label and its children are lifted alongside it, matching mkdocs-style grouping. Works in both auto-discovered and data-driven sidebars; the data path also lifts children from `merge: deep` auto-discovery. Separator labels now render `sidebar.icon` when set.
The navbar trigger is mobile-icon-only and pill at md+ with a fixed 12rem width. Dropped into the sidebar, it left a near-empty button on mobile and didn't fill the sidebar width on desktop. Add specificity-scoped overrides keyed off .hextra-sidebar-search-item to always show the label + kbd hint and stretch to full width. Rules live in search.css (unlayered) because sidebar.css is wrapped in @layer components and would otherwise lose the cascade.
Remove the site.Params.sidebar.cache toggle and the dual rendering paths. The sidebar now always renders statically via partialCached, with active state, ancestor expansion, TOC injection, and open-state persistence handled client-side. Also inline the single-use render-children wrapper into render-tree, collapsing the render-tree -> render-children -> render-tree indirection into a single recursive partial.
… fallback - Extract collapsible-button, sorted-items, and utils/sidebar-link partials to deduplicate logic across the tree-building and rendering partials - Support configurable sidebar.maxDepth (defaults to 4) in place of the hardcoded depth limit - Add a <noscript> fallback that expands all sections and hides the inert toggles for no-JS users - Delegate the mobile in-page link dismissal to the sidebar container so dynamically injected TOC links are covered - Restore saved scroll position before scrolling the active item into view - Add level/section cache keys to partialCached calls for correct caching
- Filter data-file nodes with `exclude: true` (was silently ignored) - Mark the active link with aria-current="page" in both the inline and external init scripts (WCAG) - Normalize trailing slashes when matching the active link, restoring the server-side behavior dropped in the rewrite - Drop the page-dependent mobile $groupOpen that was baked into a pageURL-agnostic partialCached result; keep groups closed and let JS open the active ancestors - Remove the now-dead pageURL param threaded through build-tree/ build-mobile-tree/sidebar - Precompute the sidebar link class so conditionals no longer leak literal newlines into the rendered class attribute - Reuse utils/sidebar-link.html in render-footer instead of duplicating the link normalization - Guard the scroll sessionStorage access against sandboxed/private contexts
Drop the string form of sidebar.separator in favor of the boolean-only contract, thread allowSeparator through the tree builders so lifting only happens at the top level, and lift separator children in the mobile tree to match desktop. Top-level data/sidebar.yaml lists now drive the sidebar for root-level docs sites, covered by a new integration test.
✅ Deploy Preview for hugo-hextra ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Owner
Author
|
Need to do some regression tests and ensure the semantics support different use cases |
Resolve data file from the section context instead of the viewed page so multi-root sites don't cross-contaminate sidebar entries. Guard on pre-filter sidebarData length so an all-excluded data file stays authoritative (returns empty) instead of silently falling back to the auto tree. Clear the body scroll lock and menu state in syncAriaHidden when the viewport transitions to desktop width.
# Conflicts: # assets/css/compiled/main.css # layouts/_partials/sidebar.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reworks the sidebar into a modular, data-driven system. The monolithic
layouts/_partials/sidebar.html(319 lines) is replaced by a pipeline ofsmall, cached partials under
layouts/_partials/components/sidebar/, and a newoptional data layer lets authors define sidebar structure explicitly instead of
relying solely on the auto-generated content tree.
What's new
Data-driven sidebar
sidebardata file underdata/<lang>/(localized) ordata/(language-independent). Any data format Hugo supports works — YAML,JSON, or TOML (e.g.
sidebar.yaml,sidebar.json,sidebar.toml); examplesbelow use YAML.
data/<lang>/sidebar/<section>.*mirroring the content tree; lookup walks from the deepest matching section
upward, so a more specific file wins. Sections without a file fall back to
auto-discovery.
none(explicit items only, default) ordeep(pin explicit items, then append remaining auto-generated children).
search(inline sidebar search input) andseparator(label; children are lifted to the parent level).
collapsible toggle when they have children — a way to expose folders that
have no
_index.md.Front matter additions
sidebar.icon: per-page icon from Hextra's icon set (data-file icon wins onconflict).
sidebar.separator: true: promote a top-level section to a separator label,lifting its children to the top level (URLs/breadcrumbs unchanged).
sidebar.sort:weight(default) ortitle, configurable globally(
params.sidebar.sort) or per-section, with per-section taking precedence.linkTitle: use a different label in the sidebar/breadcrumb thantitle.params.sidebar.maxDepthto cap tree depth.Behavior & UX
sessionStorage.prefers-reduced-motion.the active page.
<noscript>fallback and semantic CSS class layer for the rendered tree.Implementation notes
build-tree,build-auto-nodes,build-data-tree,build-mobile-tree,source-data,normalize-node,sorted-items,is-excluded,render-tree/-item/-footer/-toc,collapsible-button,icon.partialCachedkeyed by language/section/scope forperformance.
preserved focus indicators (per WCAG AA).
Breaking changes
menu.sidebarconfig is removed in favor of data files (the examplesite's
docs/hugo.yamlis updated accordingly).Docs & tests
guide/configuration.mdandguide/organize-files.md(data schema,node properties table, merge modes, sort order, scoping, separators, icons).
tests/sidebar.spec.tsbacked bydocs/content/docs/sidebar-lab/fixtures and asidebardata file underdata/en/sidebar/, plus a root-level data-sidebar integration test.Verification
hugobuild of the example site is clean (no warnings).