mirror of
https://github.com/Motschen/midnightdust-eu.git
synced 2025-12-15 18:15:10 +01:00
416 lines
11 KiB
Plaintext
416 lines
11 KiB
Plaintext
---
|
||
import ResponsiveToggle from './ResponsiveToggle.astro'
|
||
import { DarkMode } from 'accessible-astro-components'
|
||
import { Image } from 'astro:assets'
|
||
import logo from '../assets/img/logo.png'
|
||
---
|
||
|
||
<div id="main-navigation" class="is-desktop py-8">
|
||
<div class="container">
|
||
<a href="/" class="flex items-center gap-2 !no-underline">
|
||
<Image src={logo} alt="MidnightDust Logo" width="47" height="47" />
|
||
<span class="font-bold">MidnightDust – Mods by Motschen</span>
|
||
</a>
|
||
<div class="wrapper">
|
||
<nav class="desktop-menu" aria-label="Main navigation desktop">
|
||
<ul class="menu">
|
||
<slot />
|
||
</ul>
|
||
</nav>
|
||
<DarkMode />
|
||
<ResponsiveToggle />
|
||
</div>
|
||
<nav class="mobile-menu" aria-label="Main navigation mobile">
|
||
<ul class="menu">
|
||
<slot />
|
||
</ul>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
document.addEventListener('astro:page-load', () => {
|
||
// variables
|
||
const mainNav = document.querySelector('#main-navigation')
|
||
const mainMenu = mainNav.querySelector('ul')
|
||
const dropdownMenus = [...document.querySelectorAll('.has-dropdown button')]
|
||
|
||
// functions
|
||
const setActiveMenuItem = () => {
|
||
const mobileDesktopMenus = mainNav.querySelectorAll('nav > ul')
|
||
const currenPathname = window.location.pathname
|
||
|
||
mobileDesktopMenus.forEach((menu) => {
|
||
const menuItems = [...menu.querySelectorAll('a:not([rel*="external"])')] as HTMLAnchorElement[]
|
||
|
||
menuItems.forEach((menuItem) => {
|
||
if (currenPathname.includes(menuItem.pathname.replaceAll('/', '')) && menuItem.textContent !== 'Home') {
|
||
menuItem.classList.add('is-active')
|
||
menuItem.setAttribute('aria-current', 'page')
|
||
} else if (menuItem.textContent === 'Home' && currenPathname === '/') {
|
||
menuItem.classList.add('is-active')
|
||
menuItem.setAttribute('aria-current', 'page')
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
const checkMenuSize = () => {
|
||
const mainNavWidth = mainNav.getBoundingClientRect().width
|
||
const desktopMenuWidth = document.querySelector('.desktop-menu').getBoundingClientRect().width
|
||
const logoWidthBuffer = 300
|
||
const totalMenuWidth = Math.round(desktopMenuWidth) + logoWidthBuffer
|
||
|
||
if (totalMenuWidth >= mainNavWidth) {
|
||
mainNav.classList.remove('is-desktop')
|
||
mainNav.classList.add('is-mobile')
|
||
} else if (totalMenuWidth <= mainNavWidth) {
|
||
mainNav.classList.add('is-desktop')
|
||
mainNav.classList.remove('is-mobile')
|
||
}
|
||
}
|
||
|
||
const isOutOfViewport = (element) => {
|
||
const elementBounds = element.getBoundingClientRect()
|
||
return elementBounds.right > (window.innerWidth || document.documentElement.clientWidth)
|
||
}
|
||
|
||
const openDropdownMenu = (dropdownMenu) => {
|
||
const dropdownList = dropdownMenu.parentNode.querySelector('ul')
|
||
|
||
dropdownMenu.classList.add('show')
|
||
dropdownMenu.setAttribute('aria-expanded', 'true')
|
||
|
||
if (isOutOfViewport(dropdownList)) {
|
||
dropdownList.style.left = 'auto'
|
||
}
|
||
}
|
||
|
||
const closeDropdownMenu = (dropdownMenu) => {
|
||
dropdownMenu.classList.remove('show')
|
||
dropdownMenu.setAttribute('aria-expanded', 'false')
|
||
}
|
||
|
||
const closeAllDropdownMenus = () => {
|
||
for (let i = 0; i < dropdownMenus.length; i++) {
|
||
closeDropdownMenu(dropdownMenus[i])
|
||
}
|
||
}
|
||
|
||
const toggleDropdownMenu = (event) => {
|
||
if (event.target.getAttribute('aria-expanded') === 'false') {
|
||
closeAllDropdownMenus()
|
||
openDropdownMenu(event.target)
|
||
} else {
|
||
closeDropdownMenu(event.target)
|
||
}
|
||
}
|
||
|
||
// execution
|
||
mainMenu &&
|
||
mainMenu.addEventListener('keydown', (event) => {
|
||
const element = event.target as Element
|
||
const currentMenuItem = element.closest('li')
|
||
const menuItems = [...mainMenu.querySelectorAll('.menu-item')]
|
||
const currentDropdownMenu = element.closest('.has-dropdown button')
|
||
const currentDropdownMenuItem = element.closest('.has-dropdown li')
|
||
const currentIndex = menuItems.findIndex((item) => item === currentMenuItem)
|
||
|
||
const key = event.key
|
||
let targetItem
|
||
|
||
if (key === 'ArrowRight') {
|
||
if (menuItems.indexOf(currentMenuItem) === menuItems.length - 1) {
|
||
targetItem = menuItems[0]
|
||
} else {
|
||
targetItem = menuItems[currentIndex + 1]
|
||
}
|
||
}
|
||
|
||
if (key === 'ArrowLeft') {
|
||
if (menuItems.indexOf(currentMenuItem) === 0) {
|
||
targetItem = menuItems[menuItems.length - 1]
|
||
} else {
|
||
targetItem = menuItems[currentIndex - 1]
|
||
}
|
||
}
|
||
|
||
if (key === 'Escape') {
|
||
targetItem = menuItems[0]
|
||
}
|
||
|
||
if (currentDropdownMenu) {
|
||
const firstDropdownItem = currentDropdownMenu.nextElementSibling.querySelector('li')
|
||
|
||
if (key === 'ArrowDown') {
|
||
event.preventDefault()
|
||
openDropdownMenu(currentDropdownMenu)
|
||
targetItem = firstDropdownItem
|
||
}
|
||
|
||
if (key === 'Escape') {
|
||
closeDropdownMenu(currentDropdownMenu)
|
||
}
|
||
}
|
||
|
||
if (currentDropdownMenuItem) {
|
||
const currentDropdownList = currentDropdownMenuItem.parentNode
|
||
const dropdownMenuItems = [...currentDropdownList.querySelectorAll('li')]
|
||
const currentIndex = dropdownMenuItems.findIndex((item) => item === currentDropdownMenuItem)
|
||
|
||
if (key === 'ArrowDown') {
|
||
event.preventDefault()
|
||
|
||
if (dropdownMenuItems.indexOf(currentDropdownMenuItem as HTMLLIElement) === dropdownMenuItems.length - 1) {
|
||
targetItem = dropdownMenuItems[0]
|
||
} else {
|
||
targetItem = dropdownMenuItems[currentIndex + 1]
|
||
}
|
||
}
|
||
|
||
if (key === 'ArrowUp') {
|
||
event.preventDefault()
|
||
|
||
if (dropdownMenuItems.indexOf(currentDropdownMenuItem as HTMLLIElement) === 0) {
|
||
targetItem = dropdownMenuItems[dropdownMenuItems.length - 1]
|
||
} else {
|
||
targetItem = dropdownMenuItems[currentIndex - 1]
|
||
}
|
||
}
|
||
|
||
if (key === 'Escape') {
|
||
const currentDropdownMenu = (currentDropdownList as Element).previousElementSibling
|
||
targetItem = currentDropdownMenu.parentNode
|
||
closeAllDropdownMenus()
|
||
}
|
||
|
||
if (key === 'Tab') {
|
||
const currentDropdownMenu = (currentDropdownList as Element).previousElementSibling
|
||
|
||
if (dropdownMenuItems.indexOf(currentDropdownMenuItem as HTMLLIElement) === dropdownMenuItems.length - 1) {
|
||
closeDropdownMenu(currentDropdownMenu)
|
||
}
|
||
}
|
||
}
|
||
|
||
if (targetItem) {
|
||
targetItem.querySelector('a, button, input').focus()
|
||
}
|
||
})
|
||
|
||
dropdownMenus &&
|
||
dropdownMenus.forEach((dropdownMenu) => {
|
||
dropdownMenu.addEventListener('click', toggleDropdownMenu)
|
||
})
|
||
|
||
setActiveMenuItem()
|
||
checkMenuSize()
|
||
|
||
window.addEventListener('resize', checkMenuSize)
|
||
window.addEventListener('click', (event) => {
|
||
const element = event.target as Element
|
||
if (!element.hasAttribute('aria-haspopup') && !element.classList.contains('submenu-item')) {
|
||
closeAllDropdownMenus()
|
||
}
|
||
})
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" is:global>
|
||
@use '../assets/scss/base/breakpoint' as *;
|
||
@use '../assets/scss/base/outline' as *;
|
||
|
||
#main-navigation {
|
||
> .container {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
&.is-desktop {
|
||
.desktop-menu {
|
||
visibility: visible;
|
||
position: static;
|
||
}
|
||
|
||
.mobile-menu {
|
||
display: none;
|
||
}
|
||
|
||
.darkmode-toggle {
|
||
margin-top: -6px;
|
||
}
|
||
}
|
||
|
||
&.is-mobile {
|
||
flex-direction: column;
|
||
|
||
.mobile-menu {
|
||
display: none;
|
||
|
||
&.show {
|
||
display: block;
|
||
}
|
||
}
|
||
|
||
.desktop-menu {
|
||
visibility: hidden;
|
||
z-index: -99;
|
||
position: absolute;
|
||
left: 0;
|
||
}
|
||
|
||
.responsive-toggle {
|
||
display: block;
|
||
}
|
||
}
|
||
|
||
.wrapper {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 1rem;
|
||
}
|
||
|
||
nav {
|
||
> ul {
|
||
display: flex;
|
||
gap: 1.5rem;
|
||
list-style-type: none;
|
||
|
||
a,
|
||
button {
|
||
text-decoration: none;
|
||
font-size: 1.125rem;
|
||
line-height: 1.6875rem;
|
||
}
|
||
|
||
a:hover,
|
||
a:focus,
|
||
.is-active,
|
||
.has-dropdown > button:hover,
|
||
.has-dropdown > button:focus {
|
||
text-decoration: underline;
|
||
text-decoration-thickness: 1px;
|
||
text-decoration-style: wavy;
|
||
text-underline-offset: 7px;
|
||
}
|
||
|
||
.is-active {
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
}
|
||
|
||
.mobile-menu {
|
||
flex-basis: 100%;
|
||
padding: 2rem 0;
|
||
|
||
> ul {
|
||
flex-direction: column;
|
||
|
||
ul {
|
||
position: relative;
|
||
margin-top: 1rem;
|
||
}
|
||
}
|
||
|
||
a,
|
||
button {
|
||
display: block;
|
||
width: 100%;
|
||
padding: 0.5rem 0;
|
||
}
|
||
}
|
||
|
||
.has-dropdown {
|
||
position: relative;
|
||
|
||
> button {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
padding: 0;
|
||
margin-top: -1px;
|
||
border: none;
|
||
color: var(--action-color);
|
||
|
||
&:hover {
|
||
color: var(--action-color-state);
|
||
|
||
&::after {
|
||
border-color: var(--action-color-state);
|
||
}
|
||
}
|
||
|
||
&::after {
|
||
content: '';
|
||
width: 0.85rem;
|
||
height: 0.75em;
|
||
margin-top: -0.25rem;
|
||
border-style: solid;
|
||
border-width: 0.2em 0.2em 0 0;
|
||
border-color: var(--action-color);
|
||
transform: rotate(135deg);
|
||
}
|
||
|
||
&.show {
|
||
&::after {
|
||
margin-top: 0.25rem;
|
||
transform: rotate(-45deg);
|
||
}
|
||
|
||
~ ul {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
}
|
||
}
|
||
}
|
||
|
||
ul {
|
||
display: none;
|
||
position: absolute;
|
||
z-index: 100;
|
||
min-width: 260px;
|
||
top: 125%;
|
||
right: 0;
|
||
bottom: auto;
|
||
left: 0;
|
||
padding: 1rem;
|
||
background-color: var(--neutral-background);
|
||
border: 2px solid black;
|
||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
|
||
}
|
||
}
|
||
}
|
||
|
||
.darkmode-toggle {
|
||
padding: 0;
|
||
border: none;
|
||
|
||
svg {
|
||
width: 30px;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
svg path {
|
||
fill: var(--action-color);
|
||
}
|
||
|
||
&:hover {
|
||
svg path {
|
||
fill: var(--action-color-state);
|
||
}
|
||
}
|
||
|
||
&:focus {
|
||
@include outline;
|
||
|
||
&:not(:focus-visible) {
|
||
outline: none;
|
||
box-shadow: none;
|
||
}
|
||
}
|
||
}
|
||
</style>
|