11---
22import { Icon } from " astro-icon/components" ;
3+ import { LinkPresets } from " ../../constants/link-presets" ;
34import { type NavBarLink } from " ../../types/config" ;
45import { url } from " ../../utils/url-utils" ;
56
@@ -8,25 +9,143 @@ interface Props {
89}
910
1011const links = Astro .props .links ;
12+
13+ function resolveChildren(link : NavBarLink ): NavBarLink [] {
14+ return (
15+ link .children ?.map ((child ) =>
16+ typeof child === " number" ? LinkPresets [child ] : child ,
17+ ) ?? []
18+ );
19+ }
20+
21+ const maxLabelLength = Math .max (
22+ ... links .flatMap ((link ) => [
23+ link .name .length ,
24+ ... resolveChildren (link ).map ((child ) => child .name .length ),
25+ ]),
26+ );
1127---
12- <div id =" nav-menu-panel" class:list ={ [" float-panel float-panel-closed absolute transition-all fixed right-4 px-2 py-2" ]} >
13- { links .map ((link ) => (
14- <a href = { link .external ? link .url : url (link .url )} class = " group flex justify-between items-center py-2 pl-3 pr-1 rounded-lg gap-8
15- hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition
16- "
17- target = { link .external ? " _blank" : null }
18- >
19- <div class = " transition text-black/75 dark:text-white/75 font-bold group-hover:text-[var(--primary)] group-active:text-[var(--primary)]" >
20- { link .name }
28+ <div
29+ id =" nav-menu-panel"
30+ style ={ ` --nav-menu-max-label-length: ${Math .max (6 , maxLabelLength )} ` }
31+ class:list ={ [" float-panel float-panel-closed absolute transition-all fixed right-4 px-2 py-2 overflow-hidden" ]}
32+ >
33+ { links .map ((link ) => {
34+ const children = resolveChildren (link );
35+ const hasChildren = children .length > 0 ;
36+
37+ if (! hasChildren ) {
38+ return (
39+ <a href = { link .external ? link .url : url (link .url )} class = " group flex justify-between items-center py-2 pl-3 pr-1 rounded-lg gap-3 min-w-0
40+ hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition
41+ "
42+ target = { link .external ? " _blank" : null }
43+ >
44+ <div class = " transition text-black/75 dark:text-white/75 font-bold group-hover:text-[var(--primary)] group-active:text-[var(--primary)] truncate" >
45+ { link .name }
46+ </div >
47+ { ! link .external && <Icon name = " material-symbols:chevron-right-rounded"
48+ class = " transition text-[1.25rem] text-[var(--primary)]"
49+ >
50+ </Icon >}
51+ { link .external && <Icon name = " fa6-solid:arrow-up-right-from-square"
52+ class = " transition text-[0.75rem] text-black/25 dark:text-white/25 -translate-x-1"
53+ >
54+ </Icon >}
55+ </a >
56+ );
57+ }
58+
59+ return (
60+ <div class = " flex flex-col" >
61+ <button
62+ type = " button"
63+ class = " group flex w-full justify-between items-center py-2 pl-3 pr-1 rounded-lg gap-3 min-w-0
64+ hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition"
65+ data-nav-parent
66+ aria-expanded = " false"
67+ >
68+ <div class = " transition text-black/75 dark:text-white/75 font-bold group-hover:text-[var(--primary)] group-active:text-[var(--primary)] truncate" >
69+ { link .name }
70+ </div >
71+ <Icon
72+ name = " material-symbols:keyboard-arrow-down-rounded"
73+ class = " transition text-[1.25rem] text-[var(--primary)] -translate-x-0.5"
74+ data-nav-arrow
75+ />
76+ </button >
77+ <div class = " hidden pl-3" data-nav-submenu >
78+ { children .map ((child ) => (
79+ <a
80+ href = { child .external ? child .url : url (child .url )}
81+ target = { child .external ? " _blank" : null }
82+ rel = { child .external ? " noopener noreferrer" : null }
83+ class = " group flex justify-between items-center py-2 pl-3 pr-1 rounded-lg gap-3 min-w-0
84+ hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition"
85+ >
86+ <div class = " transition text-black/75 dark:text-white/75 font-medium group-hover:text-[var(--primary)] group-active:text-[var(--primary)] truncate" >
87+ { child .name }
88+ </div >
89+ { ! child .external && (
90+ <Icon
91+ name = " material-symbols:chevron-right-rounded"
92+ class = " transition text-[1.25rem] text-[var(--primary)]"
93+ />
94+ )}
95+ { child .external && (
96+ <Icon
97+ name = " fa6-solid:arrow-up-right-from-square"
98+ class = " transition text-[0.75rem] text-black/25 dark:text-white/25 -translate-x-1"
99+ />
100+ )}
101+ </a >
102+ ))}
103+ </div >
21104 </div >
22- { ! link .external && <Icon name = " material-symbols:chevron-right-rounded"
23- class = " transition text-[1.25rem] text-[var(--primary)]"
24- >
25- </Icon >}
26- { link .external && <Icon name = " fa6-solid:arrow-up-right-from-square"
27- class = " transition text-[0.75rem] text-black/25 dark:text-white/25 -translate-x-1"
28- >
29- </Icon >}
30- </a >
31- ))}
105+ );
106+ })}
32107</div >
108+
109+ <script >
110+ function initNavMenuPanel() {
111+ const panel = document.getElementById("nav-menu-panel");
112+ if (!panel) return;
113+
114+ const parents = panel.querySelectorAll("[data-nav-parent]");
115+ parents.forEach((btn) => {
116+ if (!(btn instanceof HTMLButtonElement)) return;
117+ if (btn.dataset.bound === "1") return;
118+ btn.dataset.bound = "1";
119+
120+ btn.addEventListener("click", (e) => {
121+ e.preventDefault();
122+ const container = btn.parentElement;
123+ const submenu = container?.querySelector("[data-nav-submenu]");
124+ if (!(submenu instanceof HTMLElement)) return;
125+
126+ const expanded = btn.getAttribute("aria-expanded") === "true";
127+ btn.setAttribute("aria-expanded", String(!expanded));
128+ submenu.classList.toggle("hidden", expanded);
129+
130+ const arrow = btn.querySelector("[data-nav-arrow]");
131+ if (arrow instanceof HTMLElement) {
132+ arrow.classList.toggle("rotate-180", !expanded);
133+ }
134+ });
135+ });
136+ }
137+
138+ initNavMenuPanel();
139+ document.addEventListener("content:replace", initNavMenuPanel);
140+ document.addEventListener("astro:after-swap", initNavMenuPanel);
141+ </script >
142+
143+ <style >
144+ #nav-menu-panel {
145+ width: clamp(
146+ 10rem,
147+ calc(var(--nav-menu-max-label-length) * 1.1ch + 4.75rem),
148+ calc(100vw - 2rem)
149+ );
150+ }
151+ </style >
0 commit comments