feat: child menu support in navbar (#695)

* feat: implement child menu support in main navbar

- Added a new JavaScript file for handling dropdown functionality in the navbar.
- Implemented event listeners for toggling dropdowns, closing them on outside clicks, and dismissing with the Escape key.
- Updated navbar HTML to support dropdown items with children, enhancing the navigation experience.
- Adjusted core script imports to include the new dropdown functionality.

* chore: update menu identifiers and add missing translations for development versions

* chore: update hugo stats

* chore: update script name

* chore: update menu item names to include arrows for external links
This commit is contained in:
Xin
2025-06-01 17:33:45 +01:00
committed by GitHub
parent a44de285b2
commit c24d55ee40
8 changed files with 145 additions and 18 deletions

61
assets/js/nav-menu.js Normal file
View File

@ -0,0 +1,61 @@
(function () {
const dropdownToggles = document.querySelectorAll(".hextra-nav-menu-toggle");
dropdownToggles.forEach((toggle) => {
toggle.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
// Close all other dropdowns first
dropdownToggles.forEach((otherToggle) => {
if (otherToggle !== toggle) {
otherToggle.dataset.state = "closed";
const otherMenuItems = otherToggle.nextElementSibling;
otherMenuItems.classList.add("hx:hidden");
}
});
// Toggle current dropdown
const isOpen = toggle.dataset.state === "open";
toggle.dataset.state = isOpen ? "closed" : "open";
const menuItemsElement = toggle.nextElementSibling;
if (!isOpen) {
// Position dropdown centered with toggle
menuItemsElement.style.position = "absolute";
menuItemsElement.style.top = "100%";
menuItemsElement.style.left = "50%";
menuItemsElement.style.transform = "translateX(-50%)";
menuItemsElement.style.zIndex = "1000";
// Show dropdown
menuItemsElement.classList.remove("hx:hidden");
} else {
// Hide dropdown
menuItemsElement.classList.add("hx:hidden");
}
});
});
// Dismiss dropdown when clicking outside
document.addEventListener("click", (e) => {
if (e.target.closest(".hextra-nav-menu-toggle") === null) {
dropdownToggles.forEach((toggle) => {
toggle.dataset.state = "closed";
const menuItemsElement = toggle.nextElementSibling;
menuItemsElement.classList.add("hx:hidden");
});
}
});
// Close dropdowns on escape key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
dropdownToggles.forEach((toggle) => {
toggle.dataset.state = "closed";
const menuItemsElement = toggle.nextElementSibling;
menuItemsElement.classList.add("hx:hidden");
});
}
});
})();

View File

@ -58,7 +58,7 @@ markup:
extensions: extensions:
passthrough: passthrough:
delimiters: delimiters:
block: [['\[', '\]'], ['$$', '$$']] block: [['\[', '\]'], ["$$", "$$"]]
inline: [['\(', '\)']] inline: [['\(', '\)']]
enable: true enable: true
@ -70,9 +70,8 @@ menu:
name: Documentation name: Documentation
pageRef: /docs pageRef: /docs
weight: 1 weight: 1
- identifier: showcase - identifier: versions
name: Showcase name: Versions
pageRef: /showcase
weight: 2 weight: 2
- identifier: blog - identifier: blog
name: Blog name: Blog
@ -82,15 +81,27 @@ menu:
name: About name: About
pageRef: /about pageRef: /about
weight: 4 weight: 4
- name: Search - identifier: showcase
name: Showcase
pageRef: /showcase
weight: 5 weight: 5
- name: Search
weight: 6
params: params:
type: search type: search
- name: GitHub - name: GitHub
weight: 6 weight: 7
url: "https://github.com/imfing/hextra" url: "https://github.com/imfing/hextra"
params: params:
icon: github icon: github
- identifier: development
name: Development ↗
url: https://imfing.github.io/hextra/versions/latest/
parent: versions
- identifier: v0.9
name: v0.9 ↗
url: https://imfing.github.io/hextra/versions/v0.9/
parent: versions
sidebar: sidebar:
- identifier: more - identifier: more

View File

@ -121,6 +121,9 @@
"hextra-max-footer-width", "hextra-max-footer-width",
"hextra-max-navbar-width", "hextra-max-navbar-width",
"hextra-max-page-width", "hextra-max-page-width",
"hextra-nav-menu-item",
"hextra-nav-menu-items",
"hextra-nav-menu-toggle",
"hextra-pdf", "hextra-pdf",
"hextra-scrollbar", "hextra-scrollbar",
"hextra-sidebar-collapsible-button", "hextra-sidebar-collapsible-button",
@ -311,6 +314,7 @@
"hx:duration-200", "hx:duration-200",
"hx:duration-75", "hx:duration-75",
"hx:ease-in", "hx:ease-in",
"hx:ease-in-out",
"hx:first:mt-0", "hx:first:mt-0",
"hx:flex", "hx:flex",
"hx:flex-col", "hx:flex-col",

View File

@ -4,3 +4,5 @@ blog: "وبلاگ"
about: "درباره ما" about: "درباره ما"
more: "بیشتر" more: "بیشتر"
hugoDocs: "مستندات هیوگو ↖" hugoDocs: "مستندات هیوگو ↖"
versions: "نسخه‌ها"
development: "آخرین نسخه توسعه‌ای"

View File

@ -4,3 +4,5 @@ blog: "ブログ"
about: "概要" about: "概要"
more: "もっと見る" more: "もっと見る"
hugoDocs: "Hugo ドキュメント ↗" hugoDocs: "Hugo ドキュメント ↗"
versions: "バージョン"
development: "最新の開発版"

View File

@ -4,3 +4,5 @@ blog: "博客"
about: "关于" about: "关于"
more: "更多" more: "更多"
hugoDocs: "Hugo 文档 ↗" hugoDocs: "Hugo 文档 ↗"
versions: "版本"
development: "最新开发版本"

View File

@ -13,8 +13,11 @@
{{ end -}} {{ end -}}
{{- end -}} {{- end -}}
<div class="nav-container hx:sticky hx:top-0 hx:z-20 hx:w-full hx:bg-transparent hx:print:hidden"> <div class="nav-container hx:sticky hx:top-0 hx:z-20 hx:w-full hx:bg-transparent hx:print:hidden">
<div class="nav-container-blur hx:pointer-events-none hx:absolute hx:z-[-1] hx:h-full hx:w-full hx:bg-white hx:dark:bg-dark hx:shadow-[0_2px_4px_rgba(0,0,0,.02),0_1px_0_rgba(0,0,0,.06)] hx:contrast-more:shadow-[0_0_0_1px_#000] hx:dark:shadow-[0_-1px_0_rgba(255,255,255,.1)_inset] hx:contrast-more:dark:shadow-[0_0_0_1px_#fff]"></div> <div
class="nav-container-blur hx:pointer-events-none hx:absolute hx:z-[-1] hx:h-full hx:w-full hx:bg-white hx:dark:bg-dark hx:shadow-[0_2px_4px_rgba(0,0,0,.02),0_1px_0_rgba(0,0,0,.06)] hx:contrast-more:shadow-[0_0_0_1px_#000] hx:dark:shadow-[0_-1px_0_rgba(255,255,255,.1)_inset] hx:contrast-more:dark:shadow-[0_0_0_1px_#fff]"
></div>
<nav class="hextra-max-navbar-width hx:mx-auto hx:flex hx:items-center hx:justify-end hx:gap-2 hx:h-16 hx:px-6"> <nav class="hextra-max-navbar-width hx:mx-auto hx:flex hx:items-center hx:justify-end hx:gap-2 hx:h-16 hx:px-6">
<a class="hx:flex hx:items-center hx:hover:opacity-75 hx:ltr:mr-auto hx:rtl:ml-auto" href="{{ $logoLink }}"> <a class="hx:flex hx:items-center hx:hover:opacity-75 hx:ltr:mr-auto hx:rtl:ml-auto" href="{{ $logoLink }}">
@ -51,6 +54,46 @@
{{- else -}} {{- else -}}
{{- $active := or ($currentPage.HasMenuCurrent "main" .) ($currentPage.IsMenuCurrent "main" .) -}} {{- $active := or ($currentPage.HasMenuCurrent "main" .) ($currentPage.IsMenuCurrent "main" .) -}}
{{- $activeClass := cond $active "hx:font-medium" "hx:text-gray-600 hx:hover:text-gray-800 hx:dark:text-gray-400 hx:dark:hover:text-gray-200" -}} {{- $activeClass := cond $active "hx:font-medium" "hx:text-gray-600 hx:hover:text-gray-800 hx:dark:text-gray-400 hx:dark:hover:text-gray-200" -}}
{{- if .HasChildren -}}
{{/* Dropdown menu for items with children */}}
<div class="hx:relative hx:hidden hx:md:inline-block">
<button
title="{{ or (T .Identifier) .Name | safeHTML }}"
data-state="closed"
class="hextra-nav-menu-toggle hx:cursor-pointer hx:text-sm hx:contrast-more:text-gray-700 hx:contrast-more:dark:text-gray-100 hx:relative hx:-ml-2 hx:whitespace-nowrap hx:p-2 hx:flex hx:items-center hx:gap-1 {{ $activeClass }}"
type="button"
aria-label="{{ or (T .Identifier) .Name | safeHTML }}"
>
<span class="hx:text-center">{{ or (T .Identifier) .Name | safeHTML }}</span>
{{- partial "utils/icon.html" (dict "name" "chevron-down" "attributes" "height=12 class=\"hx:transition-transform hx:duration-200 hx:ease-in-out\"") -}}
</button>
<ul
class="hextra-nav-menu-items hx:hidden hx:z-20 hx:max-h-64 hx:overflow-auto hx:rounded-md hx:ring-1 hx:ring-black/5 hx:bg-white hx:py-1 hx:text-sm hx:shadow-lg hx:dark:ring-white/20 hx:dark:bg-neutral-800"
style="min-width: 100px;"
>
{{ range .Children }}
{{- $link := .URL -}}
{{- $external := strings.HasPrefix $link "http" -}}
{{- with .PageRef -}}
{{- if hasPrefix . "/" -}}
{{- $link = relLangURL (strings.TrimPrefix "/" .) -}}
{{- end -}}
{{- end -}}
<li class="hextra-nav-menu-item hx:flex hx:flex-col">
<a
href="{{ $link }}"
{{ if $external }}target="_blank" rel="noreferrer"{{ end }}
class="hx:text-gray-600 hx:hover:text-gray-800 hx:dark:text-gray-400 hx:dark:hover:text-gray-200 hx:relative hx:cursor-pointer hx:whitespace-nowrap hx:py-1.5 hx:transition-colors hx:ltr:pl-3 hx:ltr:pr-9 hx:rtl:pr-3 hx:rtl:pl-9"
>
{{- or (T .Identifier) .Name | safeHTML -}}
</a>
</li>
{{- end -}}
</ul>
</div>
{{- else -}}
{{/* Regular menu item without children */}}
<a <a
title="{{ or (T .Identifier) .Name | safeHTML }}" title="{{ or (T .Identifier) .Name | safeHTML }}"
href="{{ $link }}" href="{{ $link }}"
@ -62,6 +105,7 @@
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
{{- end -}}
<button type="button" aria-label="Menu" class="hamburger-menu hx:cursor-pointer hx:-mr-2 hx:rounded-sm hx:p-2 hx:active:bg-gray-400/20 hx:md:hidden"> <button type="button" aria-label="Menu" class="hamburger-menu hx:cursor-pointer hx:-mr-2 hx:rounded-sm hx:p-2 hx:active:bg-gray-400/20 hx:md:hidden">

View File

@ -2,12 +2,13 @@
{{- $jsMenu := resources.Get "js/menu.js" -}} {{- $jsMenu := resources.Get "js/menu.js" -}}
{{- $jsTabs := resources.Get "js/tabs.js" -}} {{- $jsTabs := resources.Get "js/tabs.js" -}}
{{- $jsLang := resources.Get "js/lang.js" -}} {{- $jsLang := resources.Get "js/lang.js" -}}
{{- $jsNavMenu := resources.Get "js/nav-menu.js" -}}
{{- $jsCodeCopy := resources.Get "js/code-copy.js" -}} {{- $jsCodeCopy := resources.Get "js/code-copy.js" -}}
{{- $jsFileTree := resources.Get "js/filetree.js" -}} {{- $jsFileTree := resources.Get "js/filetree.js" -}}
{{- $jsSidebar := resources.Get "js/sidebar.js" -}} {{- $jsSidebar := resources.Get "js/sidebar.js" -}}
{{- $jsBackToTop := resources.Get "js/back-to-top.js" -}} {{- $jsBackToTop := resources.Get "js/back-to-top.js" -}}
{{- $scripts := slice $jsTheme $jsMenu $jsCodeCopy $jsTabs $jsLang $jsFileTree $jsSidebar $jsBackToTop | resources.Concat "js/main.js" -}} {{- $scripts := slice $jsTheme $jsMenu $jsCodeCopy $jsTabs $jsLang $jsNavMenu $jsFileTree $jsSidebar $jsBackToTop | resources.Concat "js/main.js" -}}
{{- if hugo.IsProduction -}} {{- if hugo.IsProduction -}}
{{- $scripts = $scripts | minify | fingerprint -}} {{- $scripts = $scripts | minify | fingerprint -}}
{{- end -}} {{- end -}}