Files
midnightdust-eu/src/components/Navigation.astro
2024-02-08 12:01:49 +01:00

416 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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>