zwischenstand
This commit is contained in:
@@ -1,125 +1,247 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte"
|
||||
import { getDBEntry } from "../../../api"
|
||||
import { navigationCache, location, categories, overlays } from "../../store"
|
||||
import DesktopHeader from "./Desktop.svelte"
|
||||
import {
|
||||
getBCGraphCategories,
|
||||
mapBigcommerceCategoriesToNavigation,
|
||||
} from "../../functions/CommerceAPIs/bigCommerce/categories"
|
||||
import Banner from "../widgets/Banner.svelte"
|
||||
import { getDBEntries } from "../../../api"
|
||||
import { spaLink } from "../../actions"
|
||||
import { location } from "../../store"
|
||||
|
||||
let navigationElements: NavigationElement[] = [],
|
||||
navOpen = false,
|
||||
subNavOpen: { [key: number]: boolean } ={},
|
||||
windowWidth: number
|
||||
const NAVIGATION_TYPE = {
|
||||
Main: 0,
|
||||
Service: 1,
|
||||
Legal: 2,
|
||||
} as const
|
||||
|
||||
function elementsToCache(elements: NavigationElement[]) {
|
||||
elements.forEach((el) => {
|
||||
if (!el.external) {
|
||||
if (!$navigationCache[el.page]) $navigationCache[el.page] = el
|
||||
if (el.elements?.length > 0) elementsToCache(el.elements)
|
||||
}
|
||||
})
|
||||
let navigationEntries: NavigationEntry[] = []
|
||||
let isMenuOpen = false
|
||||
let loadingNavigation = true
|
||||
|
||||
const resolveHref = (item: NavigationElement) => {
|
||||
const base = item.page || "/"
|
||||
const hash = item.hash ? (item.hash.startsWith("#") ? item.hash : `#${item.hash}`) : ""
|
||||
return `${base}${hash}`
|
||||
}
|
||||
|
||||
getDBEntry("navigation", {
|
||||
tree: 0,
|
||||
}).then((nav) => {
|
||||
navigationElements.push(...nav.elements)
|
||||
navigationElements = navigationElements
|
||||
})
|
||||
|
||||
$: if (!navOpen) subNavOpen ={}
|
||||
$: if ($location) navOpen = false
|
||||
|
||||
let scrolled: boolean = false,
|
||||
isHomepage: boolean = false,
|
||||
bannerVisible = false
|
||||
$: isHomepage = !$location.path || $location.path === "/"
|
||||
|
||||
function checkScroll() {
|
||||
scrolled = window.scrollY >= 100
|
||||
const isActive = (item: NavigationElement) => {
|
||||
const target = resolveHref(item)
|
||||
const [path] = target.split("#")
|
||||
return path === $location.path
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
checkScroll()
|
||||
window.addEventListener("scroll", checkScroll)
|
||||
return () => {
|
||||
window.removeEventListener("scroll", checkScroll)
|
||||
}
|
||||
const closeMenu = () => {
|
||||
isMenuOpen = false
|
||||
}
|
||||
|
||||
const toggleMenu = () => {
|
||||
isMenuOpen = !isMenuOpen
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const entries = await getDBEntries("navigation")
|
||||
navigationEntries = entries ?? []
|
||||
} catch (error) {
|
||||
console.error("Unable to load navigation", error)
|
||||
} finally {
|
||||
loadingNavigation = false
|
||||
}
|
||||
})
|
||||
$: {
|
||||
if (typeof window !== "undefined" && $location) {
|
||||
checkScroll()
|
||||
}
|
||||
}
|
||||
|
||||
let activeSubmenu = -1
|
||||
$: darkBG = isHomepage ? (scrolled ? true : activeSubmenu >= 0 || $overlays?.length) : false
|
||||
$: mainNavigation = navigationEntries.find((entry) => Number(entry.type) === NAVIGATION_TYPE.Main)
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerWidth={windowWidth} />
|
||||
|
||||
<Banner bind:isVisible={bannerVisible} />
|
||||
<header
|
||||
class="headercontainer"
|
||||
id={'header-container'}
|
||||
class:scrolled={darkBG}
|
||||
class:homepageHeader={isHomepage}
|
||||
class:bannerVisible={bannerVisible}
|
||||
role="dialog"
|
||||
aria-label="Hauptnavigation"
|
||||
on:focus
|
||||
>
|
||||
<div class="padding">
|
||||
{#key [bannerVisible]}
|
||||
<DesktopHeader
|
||||
bind:activeSubmenu={activeSubmenu}
|
||||
elements={navigationElements}
|
||||
bannerVisible={bannerVisible}
|
||||
scrolled={darkBG}
|
||||
/>
|
||||
{/key}
|
||||
<header class="site-header" aria-label="Primäre Navigation">
|
||||
<div class="header-inner">
|
||||
<a
|
||||
class="brand"
|
||||
href="/"
|
||||
use:spaLink
|
||||
on:click={closeMenu}
|
||||
>
|
||||
Kontextwerk
|
||||
</a>
|
||||
<button
|
||||
class="menu-toggle"
|
||||
aria-expanded={isMenuOpen}
|
||||
aria-controls="primary-navigation"
|
||||
on:click={toggleMenu}
|
||||
>
|
||||
<span class="sr-only">Navigation umschalten</span>
|
||||
<span class="bar"></span>
|
||||
<span class="bar"></span>
|
||||
<span class="bar"></span>
|
||||
</button>
|
||||
<nav
|
||||
id="primary-navigation"
|
||||
class:open={isMenuOpen}
|
||||
>
|
||||
{#if loadingNavigation}
|
||||
<span class="nav-placeholder">Navigation wird geladen …</span>
|
||||
{:else if mainNavigation?.elements?.length}
|
||||
<ul>
|
||||
{#each mainNavigation.elements as item (item.name)}
|
||||
<li class:active={isActive(item)}>
|
||||
{#if item.external && item.externalUrl}
|
||||
<a
|
||||
href={item.externalUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
on:click={closeMenu}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
{:else}
|
||||
<a
|
||||
href={resolveHref(item)}
|
||||
use:spaLink
|
||||
on:click={closeMenu}
|
||||
>
|
||||
{item.name}
|
||||
</a>
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{:else}
|
||||
<span class="nav-placeholder">Keine Navigationspunkte verfügbar</span>
|
||||
{/if}
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<style lang="less">
|
||||
@import "../../assets/css/variables.less";
|
||||
|
||||
@desktop: ~"only screen and (min-width: 1440px)";
|
||||
|
||||
.headercontainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@media @mobile {
|
||||
overflow: hidden;
|
||||
}
|
||||
.site-header {
|
||||
position: sticky;
|
||||
z-index: 5500;
|
||||
top: 0px;
|
||||
|
||||
justify-content: space-between;
|
||||
background-color: #0d0c0c;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
background-color: var(--neutral-white);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.header-inner {
|
||||
max-width: var(--body-maxwidth);
|
||||
margin: 0 auto;
|
||||
padding: 1.2rem 1.6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
.padding {
|
||||
width: 100%;
|
||||
padding: 0px var(--horizontal-default-margin);
|
||||
height: 100%;
|
||||
justify-content: space-between;
|
||||
gap: 1.2rem;
|
||||
}
|
||||
|
||||
.brand {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--text-invers-100);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.menu-toggle {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.6rem;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
|
||||
.bar {
|
||||
width: 1.6rem;
|
||||
height: 2px;
|
||||
background-color: var(--text-invers-100);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
ul {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
gap: 1.2rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
&.homepageHeader {
|
||||
background-color: transparent;
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
|
||||
&.active a::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
&.scrolled&.homepageHeader {
|
||||
box-shadow: 0px 10px 20px 0px rgba(0, 0, 0, 0.2);
|
||||
background-color: var(--bg-100);
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
color: var(--text-100);
|
||||
padding: 0.3rem 0;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -0.3rem;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: var(--accent-100, #c4102d);
|
||||
transform: scaleX(0);
|
||||
transform-origin: left;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-placeholder {
|
||||
font-size: 0.875rem;
|
||||
color: var(--text-60);
|
||||
}
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.menu-toggle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
nav {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: var(--neutral-white);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
transform: translateY(-110%);
|
||||
transition: transform 0.2s ease;
|
||||
padding: 1.2rem 1.6rem;
|
||||
|
||||
&.open {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
ul {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user