Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: render sidebar from data #376

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
19 changes: 17 additions & 2 deletions assets/css/components/sidebar.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@media (max-width: 767px) {
.sidebar-container {
.hextra-sidebar-container {
@apply hx-fixed hx-pt-[calc(var(--navbar-height))] hx-top-0 hx-w-full hx-bottom-0 hx-z-[15] hx-overscroll-contain hx-bg-white dark:hx-bg-dark;
transition: transform 0.8s cubic-bezier(0.52, 0.16, 0.04, 1);
will-change: transform, opacity;
Expand All @@ -8,7 +8,7 @@
}
}

.sidebar-container {
.hextra-sidebar-container {
li > div {
@apply hx-h-0;
}
Expand All @@ -18,4 +18,19 @@
li.open > a > span > svg > path {
@apply hx-rotate-90;
}

.hextra-sidebar-item-list {
@apply hx-relative hx-flex hx-flex-col hx-gap-1 before:hx-absolute before:hx-inset-y-1 before:hx-w-px before:hx-bg-gray-200 ltr:hx-ml-3 ltr:hx-pl-3 ltr:before:hx-left-0 rtl:hx-mr-3 rtl:hx-pr-3 rtl:before:hx-right-0 dark:before:hx-bg-neutral-800;
}

.hextra-sidebar-item-link {
@apply hx-flex hx-items-center hx-justify-between hx-gap-2 hx-cursor-pointer hx-rounded hx-px-2 hx-py-1.5 hx-text-sm hx-transition-colors;

&.active {
@apply hx-bg-primary-100 hx-font-semibold hx-text-primary-800 contrast-more:hx-border contrast-more:hx-border-primary-500 dark:hx-bg-primary-400/10 dark:hx-text-primary-600 contrast-more:dark:hx-border-primary-500;
}
&.inactive {
@apply hx-text-gray-500 hover:hx-bg-gray-100 hover:hx-text-gray-900 contrast-more:hx-border contrast-more:hx-border-transparent contrast-more:hx-text-gray-900 contrast-more:hover:hx-border-gray-900 dark:hx-text-neutral-400 dark:hover:hx-bg-primary-100/5 dark:hover:hx-text-gray-50 contrast-more:dark:hx-text-gray-50 contrast-more:dark:hover:hx-border-gray-50;
}
}
}
2 changes: 1 addition & 1 deletion assets/js/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
document.addEventListener('DOMContentLoaded', function () {
const menu = document.querySelector('.hamburger-menu');
const overlay = document.querySelector('.mobile-menu-overlay');
const sidebarContainer = document.querySelector('.sidebar-container');
const sidebarContainer = document.querySelector('.hextra-sidebar-container');

// Initialize the overlay
const overlayClasses = ['hx-fixed', 'hx-inset-0', 'hx-z-10', 'hx-bg-black/80', 'dark:hx-bg-black/60'];
Expand Down
46 changes: 44 additions & 2 deletions assets/js/sidebar.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
/**
* Check if the element is visible.
* @param {Element} element Dom element
* @returns boolean
*/
function isVisible(element) {
return element.offsetWidth > 0 || element.offsetHeight > 0;
}

document.addEventListener("DOMContentLoaded", function () {
scrollToActiveItem();
enableCollapsibles();
Expand All @@ -10,10 +19,43 @@ function enableCollapsibles() {
e.preventDefault();
const list = button.parentElement.parentElement;
if (list) {
list.classList.toggle("open")
list.classList.toggle("open");
}
});
});

const isCached = "{{- site.Params.page.sidebar.cache | default false -}}" === "true";
const currentPagePath = window.location.href;

if (isCached) {
// find the current page in the sidebar and open the parent lists
const sidebar = document.querySelector(".hextra-sidebar-container");
if (sidebar) {
// find a tags and compare href with current page path
const links = sidebar.querySelectorAll("a");
links.forEach(function (link) {
const linkPath = link.href;

if (currentPagePath === linkPath) {
// add active class to the link
link.classList.add("active");
link.classList.remove("inactive");

if (!isVisible(link)) {
return;
}
// recursively open parent lists
let parent = link.parentElement;
while (parent && !parent.classList.contains("hextra-sidebar-container")) {
if (parent.tagName === "LI" && parent.classList.contains("hextra-sidebar-item")) {
parent.classList.add("open");
}
parent = parent.parentElement;
}
}
});
}
}
}

function scrollToActiveItem() {
Expand All @@ -31,6 +73,6 @@ function scrollToActiveItem() {
const yDistance = visibleActiveItem.getBoundingClientRect().top - sidebarScrollbar.getBoundingClientRect().top;
sidebarScrollbar.scrollTo({
behavior: "instant",
top: yDistance - yOffset
top: yDistance - yOffset,
});
}
12 changes: 12 additions & 0 deletions layouts/partials/components/sidebar/bottom.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{- range site.Menus.sidebar }}
{{- $name := or (T .Identifier) .Name }}
{{- if eq .Params.type "separator" }}
<li class="[word-break:break-word] hx-mt-5 hx-mb-2 hx-px-2 hx-py-1.5 hx-text-sm hx-font-semibold hx-text-gray-900 first:hx-mt-0 dark:hx-text-gray-100">
<span class="hx-cursor-default">{{ $name }}</span>
</li>
{{- else }}
<li>
{{- partial "components/sidebar/item-link" (dict "active" false "title" $name "link" (.URL | relLangURL)) -}}
</li>
{{- end }}
{{- end -}}
5 changes: 5 additions & 0 deletions layouts/partials/components/sidebar/collapsible-button.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<span class="hextra-sidebar-collapsible-button">
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor" class="hx-h-[18px] hx-min-w-[18px] hx-rounded-sm hx-p-0.5 hover:hx-bg-gray-800/5 dark:hover:hx-bg-gray-100/5">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" class="hx-origin-center hx-transition-transform rtl:-hx-rotate-180"></path>
</svg>
</span>
51 changes: 51 additions & 0 deletions layouts/partials/components/sidebar/generate-section-data.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{{- $context := . -}}

{{- $pages := union .RegularPages .Sections -}}
{{- $pages = where $pages "Params.sidebar.exclude" "!=" true -}}

{{- $data := slice -}}

{{- range $pages.ByWeight -}}
{{ $structure := (partial "sidebar/section-walk" .) | unmarshal -}}
{{ $data = $data | append $structure -}}
{{ end -}}

{{- define "partials/sidebar/section-walk" -}}
{{- with . -}}
{
"title": "{{ partial "utils/title" . }}",
"link": "{{ .RelPermalink }}",
"toc": {{ partial "sidebar/section-page-toc" . }},
"open": {{ .Params.sidebar.open | default false }}
{{- if .IsSection }},
"items": [
{{ $pages := union .RegularPages .Sections -}}
{{ $pages = where $pages "Params.sidebar.exclude" "!=" true -}}
{{ range $index, $page := $pages.ByWeight -}}
{{ partial "sidebar/section-walk" . }}{{ if not (ge $index (sub (len $pages) 1)) }},{{ end -}}
{{ end -}}
]
{{ end -}}
}
{{- end }}
{{- end -}}

{{- define "partials/sidebar/section-page-toc" -}}
{{/* Get level 2 headings list used mainly for mobile navigation */}}
[
{{- with .Fragments.Headings -}}
{{/* Loop over level 1 headings */}}
{{- range . }}
{{- with .Headings }}
{{ $headings := . }}
{{- range $index, $heading := $headings }}
{{ $heading.Title | jsonify (dict "noHTMLEscape" true) }}
{{- if not (ge $index (sub (len $headings) 1)) }},{{ end -}}
{{ end -}}
{{- end -}}
{{ end -}}
{{- end -}}
]
{{- end -}}

{{ return ($data | jsonify (dict "noHTMLEscape" true)) }}
20 changes: 20 additions & 0 deletions layouts/partials/components/sidebar/get-section-data.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{{/* Get section sidebar config from Hugo `data` directory

If the site is multilingual, the sidebar data is stored in a language-specific
directory. For example, the English sidebar data is stored in `data/en/sidebar.yaml`.
*/}}
{{ $data := "" }}
{{ $section := .Section | default "index" }}
{{ $filename := "sidebar" }}

{{ if hugo.IsMultilingual }}
{{ with (index site.Data site.Language.Lang $filename $section) }}
{{ $data = . }}
{{ end }}
{{ else }}
{{ with (index site.Data $filename $section) }}
{{ $data = . }}
{{ end }}
{{ end }}

{{ return $data }}
18 changes: 18 additions & 0 deletions layouts/partials/components/sidebar/item-link.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{{- $external := strings.HasPrefix .link "http" -}}

{{- $activeClass := cond (.active) "active" "inactive" -}}


<a
class="hextra-sidebar-item-link {{ $activeClass }} [-webkit-tap-highlight-color:transparent] [-webkit-touch-callout:none] [word-break:break-word]"
href="{{ .link }}"
{{ if $external }}target="_blank" rel="noreferer"{{ end }}
>
{{- .title -}}
{{- with .context }}
{{- if or .RegularPages .Sections }}
{{- partialCached "components/sidebar/collapsible-button" . }}
{{- end }}
{{ end -}}
{{- with .items }}{{- partialCached "components/sidebar/collapsible-button" site.Home }}{{ end -}}
</a>
16 changes: 16 additions & 0 deletions layouts/partials/components/sidebar/render-data.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{{- $page := .page -}}
{{- $pageLink := $page.RelPermalink -}}
{{- $cached := .cached | default false }}

{{- range .data -}}
{{- $active := and (not $cached) (or (eq $pageLink .link) (eq (strings.TrimSuffix "/" $pageLink) .link)) -}}
{{- $containsPage := hasPrefix $pageLink .link -}}
{{- $shouldOpen := or (.open) $containsPage $active | default false -}}

<li class="hextra-sidebar-item {{ if $shouldOpen }}open{{ end }}">
{{- partial "components/sidebar/item-link" (dict "active" $active "title" .title "link" .link "items" .items) -}}
{{- if .items -}}
{{- partial "components/sidebar/render-items" (dict "items" .items "link" $pageLink "cached" $cached) -}}
{{- end -}}
</li>
{{ end }}
21 changes: 21 additions & 0 deletions layouts/partials/components/sidebar/render-items.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{{- $items := .items -}}
{{- $pageLink := .link -}}
{{- $cached := .cached | default false }}


<div class="ltr:hx-pr-0 hx-overflow-hidden">
<ul class="hextra-sidebar-item-list">
{{- range $items }}
{{- $active := and (not $cached) (or (eq $pageLink .link) (eq (strings.TrimSuffix "/" $pageLink) .link)) -}}
{{- $containsPage := hasPrefix $pageLink .link -}}
{{- $shouldOpen := or (.open) $containsPage $active | default false -}}

<li class="hextra-sidebar-item hx-flex hx-flex-col {{ if $shouldOpen }}open{{ end }}">
{{- partial "components/sidebar/item-link" (dict "active" $active "title" .title "link" .link "items" .items) -}}
{{- if .items -}}
{{- partial "components/sidebar/render-items" (dict "items" .items "link" $pageLink "cached" $cached) -}}
{{- end -}}
</li>
{{- end -}}
</ul>
</div>
2 changes: 1 addition & 1 deletion layouts/partials/scripts.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{{- $jsLang := resources.Get "js/lang.js" -}}
{{- $jsCodeCopy := resources.Get "js/code-copy.js" -}}
{{- $jsFileTree := resources.Get "js/filetree.js" -}}
{{- $jsSidebar := resources.Get "js/sidebar.js" -}}
{{- $jsSidebar := resources.Get "js/sidebar.js" | resources.ExecuteAsTemplate "sidebar.js" . -}}
{{- $jsBackToTop := resources.Get "js/back-to-top.js" -}}

{{- $scripts := slice $jsTheme $jsMenu $jsCodeCopy $jsTabs $jsLang $jsFileTree $jsSidebar $jsBackToTop | resources.Concat "js/main.js" -}}
Expand Down
95 changes: 95 additions & 0 deletions layouts/partials/sidebar-ng.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{{- $context := .context -}}

{{- $disableSidebar := .disableSidebar | default false -}}
{{- $displayPlaceholder := .displayPlaceholder | default false -}}

{{- $sidebarClass := cond $disableSidebar (cond $displayPlaceholder "md:hx-hidden xl:hx-block" "md:hx-hidden") "md:hx-sticky" -}}

{{- $navRoot := cond (eq site.Home.Type "docs") site.Home $context.FirstSection -}}
{{- $pageURL := $context.RelPermalink -}}

{{- $data := slice -}}
{{- $dataMobile := slice -}}

{{- if (eq site.Params.page.sidebar.source "data") -}}
{{ $data = partialCached "components/sidebar/get-section-data" $context $context.Section }}
{{- $dataMobile = $data -}}
{{- else -}}
{{- $data = (partialCached "components/sidebar/generate-section-data" $navRoot $navRoot) | unmarshal -}}
{{- $dataMobile = (partialCached "components/sidebar/generate-section-data" site.Home site.Home) | unmarshal -}}
{{- end -}}

{{- $shouldCache := site.Params.page.sidebar.cache | default false -}}

{{/* EXPERIMENTAL */}}
{{- if .context.Params.sidebar.hide -}}
{{- $disableSidebar = true -}}
{{- $displayPlaceholder = true -}}
{{- end -}}

<div class="mobile-menu-overlay [transition:background-color_1.5s_ease] hx-fixed hx-inset-0 hx-z-10 hx-bg-black/80 dark:hx-bg-black/60 hx-hidden"></div>

<aside class="hextra-sidebar-container hx-flex hx-flex-col print:hx-hidden md:hx-top-16 md:hx-shrink-0 md:hx-w-64 md:hx-self-start max-md:[transform:translate3d(0,-100%,0)] {{ $sidebarClass }}">
{{/* Search bar on small screen */}}
{{- partialCached "components/sidebar/mobile-search" . -}}


<div class="hextra-scrollbar hx-overflow-y-auto hx-overflow-x-hidden hx-p-4 hx-grow md:hx-h-[calc(100vh-var(--navbar-height)-var(--menu-height))]">
{{/* Mobile Navigation */}}
<ul class="hx-flex hx-flex-col hx-gap-1 md:hx-hidden">
{{- with $dataMobile -}}
{{- partial "components/sidebar/render-data" (dict "data" . "page" $context) -}}
{{- end -}}
</ul>

{{/* Sidebar on large screen */}}
{{- if $disableSidebar -}}
{{- if $displayPlaceholder }}<div class="max-xl:hx-hidden hx-h-0 hx-w-64 hx-shrink-0"></div>{{ end -}}
{{ .context.Scratch.Set "enableFooterSwitches" true }}
{{- else -}}
<ul class="hx-flex hx-flex-col hx-gap-1 max-md:hx-hidden">
{{- with $data -}}
{{- if $shouldCache -}}
{{- partialCached "components/sidebar/render-data" (dict "data" . "page" $context "cached" $shouldCache) $navRoot -}}
{{- else -}}
{{- partial "components/sidebar/render-data" (dict "data" . "page" $context "cached" $shouldCache) -}}
{{- end -}}
{{- end -}}
{{- partialCached "components/sidebar/bottom" $context site.Home -}}
</ul>
{{ end -}}
</div>
{{- partial "components/sidebar/switches" (dict "context" $context "disableSidebar" $disableSidebar) -}}
</aside>

{{- define "partials/components/sidebar/mobile-search" -}}
<div class="hx-px-4 hx-pt-4 md:hx-hidden">
{{- partialCached "search.html" . -}}
</div>
{{- end -}}

{{- define "partials/components/sidebar/switches" -}}
{{- $context := .context -}}
{{- $disableSidebar := .disableSidebar -}}
{{/* Hide theme switch when sidebar is disabled */}}
{{ $switchesClass := cond $disableSidebar "md:hx-hidden" "" -}}
{{ $displayThemeToggle := (site.Params.theme.displayToggle | default true) -}}

{{ if or site.IsMultiLingual $displayThemeToggle }}
<div
class="{{ $switchesClass }} {{ with site.IsMultiLingual }}
hx-justify-end
{{ end }} hx-sticky hx-bottom-0 hx-bg-white dark:hx-bg-dark hx-mx-4 hx-py-4 hx-shadow-[0_-12px_16px_#fff] hx-flex hx-items-center hx-gap-2 dark:hx-border-neutral-800 dark:hx-shadow-[0_-12px_16px_#111] contrast-more:hx-border-neutral-400 contrast-more:hx-shadow-none contrast-more:dark:hx-shadow-none hx-border-t"
data-toggle-animation="show"
>
{{- with site.IsMultiLingual -}}
{{- partial "language-switch" (dict "context" $context "grow" true) -}}
{{- with $displayThemeToggle }}{{ partial "theme-toggle" (dict "hideLabel" true) }}{{ end -}}
{{- else -}}
{{- with $displayThemeToggle -}}
<div class="hx-flex hx-grow hx-flex-col">{{ partial "theme-toggle" }}</div>
{{- end -}}
{{- end -}}
</div>
{{- end -}}
{{- end -}}