| --- | |
| import { Icon } from "astro-icon/components"; | |
| import { LinkPresets } from "@/constants/link-presets"; | |
| import { LinkPreset, type NavBarLink } from "@/types/config"; | |
| import { url } from "@/utils/url-utils"; | |
| interface Props { | |
| links: NavBarLink[]; | |
| } | |
| // 处理links中的LinkPreset转换 | |
| const processedLinks = Astro.props.links.map((link: NavBarLink) => ({ | |
| ...link, | |
| children: link.children?.map((child: NavBarLink | LinkPreset): NavBarLink => { | |
| if (typeof child === "number") { | |
| return LinkPresets[child]; | |
| } | |
| return child; | |
| }), | |
| })); | |
| --- | |
| <div id="nav-menu-panel" class:list={["float-panel float-panel-closed absolute transition-all fixed right-4 px-2 py-2 max-h-[80vh] overflow-y-auto"]}> | |
| {processedLinks.map((link) => ( | |
| <div class="mobile-menu-item"> | |
| {link.children && link.children.length > 0 ? ( | |
| <!-- 有子菜单的项目 --> | |
| <div class="mobile-dropdown" data-mobile-dropdown> | |
| <button | |
| class="group flex justify-between items-center py-2 pl-3 pr-1 rounded-lg gap-8 w-full text-left | |
| hover:bg-(--btn-plain-bg-hover) active:bg-(--btn-plain-bg-active) transition" | |
| data-mobile-dropdown-trigger | |
| aria-expanded="false" | |
| > | |
| <div class="flex items-center transition text-black/75 dark:text-white/75 font-bold group-hover:text-(--primary) group-active:text-(--primary)"> | |
| {link.icon && <Icon name={link.icon} class="text-[1.1rem] mr-2" />} | |
| {link.name} | |
| </div> | |
| <Icon name="material-symbols:keyboard-arrow-down-rounded" | |
| class="transition text-[1.25rem] text-(--primary) mobile-dropdown-arrow duration-200" | |
| /> | |
| </button> | |
| <div class="mobile-submenu" data-mobile-submenu> | |
| {link.children.map((child) => ( | |
| <a href={child.external ? child.url : url(child.url)} | |
| class="group flex justify-between items-center py-2 pl-6 pr-1 rounded-lg gap-8 | |
| hover:bg-(--btn-plain-bg-hover) active:bg-(--btn-plain-bg-active) transition" | |
| target={child.external ? "_blank" : null} | |
| > | |
| <div class="flex items-center transition text-black/60 dark:text-white/60 font-medium group-hover:text-(--primary) group-active:text-(--primary)"> | |
| {child.icon && <Icon name={child.icon} class="text-[1.1rem] mr-2" />} | |
| {child.name} | |
| </div> | |
| {child.external && <Icon name="fa7-solid:arrow-up-right-from-square" | |
| class="transition text-[0.75rem] text-black/25 dark:text-white/25 -translate-x-1" | |
| />} | |
| </a> | |
| ))} | |
| </div> | |
| </div> | |
| ) : ( | |
| <!-- 普通链接项目 --> | |
| <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 | |
| hover:bg-(--btn-plain-bg-hover) active:bg-(--btn-plain-bg-active) transition | |
| " | |
| target={link.external ? "_blank" : null} | |
| > | |
| <div class="flex items-center transition text-black/75 dark:text-white/75 font-bold group-hover:text-(--primary) group-active:text-(--primary)"> | |
| {link.icon && <Icon name={link.icon} class="text-[1.1rem] mr-2" />} | |
| {link.name} | |
| </div> | |
| {!link.external && <Icon name="material-symbols:chevron-right-rounded" | |
| class="transition text-[1.25rem] text-(--primary)" | |
| />} | |
| {link.external && <Icon name="fa7-solid:arrow-up-right-from-square" | |
| class="transition text-[0.75rem] text-black/25 dark:text-white/25 -translate-x-1" | |
| />} | |
| </a> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| <style> | |
| @reference "../../styles/main.css"; | |
| .mobile-submenu { | |
| @apply max-h-0 overflow-hidden transition-all duration-300 ease-in-out; | |
| } | |
| .mobile-dropdown[data-expanded="true"] .mobile-submenu { | |
| @apply max-h-96; | |
| } | |
| .mobile-dropdown[data-expanded="true"] .mobile-dropdown-arrow { | |
| @apply rotate-180; | |
| } | |
| </style> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const mobileDropdowns = document.querySelectorAll('[data-mobile-dropdown]'); | |
| mobileDropdowns.forEach(dropdown => { | |
| const trigger = dropdown.querySelector('[data-mobile-dropdown-trigger]'); | |
| const submenu = dropdown.querySelector('[data-mobile-submenu]'); | |
| if (!trigger || !submenu) return; | |
| trigger.addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| const isExpanded = dropdown.getAttribute('data-expanded') === 'true'; | |
| // 关闭其他打开的下拉菜单 | |
| mobileDropdowns.forEach(otherDropdown => { | |
| if (otherDropdown !== dropdown) { | |
| otherDropdown.setAttribute('data-expanded', 'false'); | |
| const otherTrigger = otherDropdown.querySelector('[data-mobile-dropdown-trigger]'); | |
| if (otherTrigger) { | |
| otherTrigger.setAttribute('aria-expanded', 'false'); | |
| } | |
| } | |
| }); | |
| // 切换当前下拉菜单 | |
| const newState = !isExpanded; | |
| dropdown.setAttribute('data-expanded', newState.toString()); | |
| trigger.setAttribute('aria-expanded', newState.toString()); | |
| }); | |
| }); | |
| }); | |
| </script> | |